Очистка формы и reset функциональность

В веб-приложениях формы играют центральную роль, а корректное тестирование их поведения — необходимый элемент обеспечения качества. Одним из часто встречающихся сценариев является проверка очистки формы после определённых действий пользователя, будь то отправка данных или сброс полей с помощью кнопки reset. React Testing Library (RTL) предоставляет инструменты для реализации таких проверок в тестах на уровне пользовательского взаимодействия, что соответствует принципу «тестировать так, как использует приложение пользователь».


Управление состоянием формы

В React формы обычно управляются через controlled components, где значения полей хранятся в состоянии компонента (useState или useReducer). Пример:

import React, { useState } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleReset = () => {
    setEmail('');
    setPassword('');
  };

  return (
    <form>
      <input
        type="email"
        value={email}
        onCha nge={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onCha nge={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      <button type="button" onCl ick={handleReset}>Reset</button>
    </form>
  );
}

export default LoginForm;

Здесь кнопка Reset вызывает функцию handleReset, которая сбрасывает состояние формы. Это классический пример, который тестируется в RTL через имитацию действий пользователя.


Базовое тестирование очистки формы

Для проверки функциональности reset используется комбинация render, fireEvent или userEvent, а также селекторов для поиска элементов формы. Пример теста:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LoginForm from './LoginForm';

test('очистка формы через кнопку reset', async () => {
  render(<LoginForm />);
  const emailInput = screen.getByPlaceholderText(/email/i);
  const passwordInput = screen.getByPlaceholderText(/password/i);
  const resetButton = screen.getByRole('button', { name: /reset/i });

  await userEvent.type(emailInput, 'test@example.com');
  await userEvent.type(passwordInput, '123456');

  expect(emailInput).toHaveValue('test@example.com');
  expect(passwordInput).toHaveValue('123456');

  await userEvent.click(resetButton);

  expect(emailInput).toHaveValue('');
  expect(passwordInput).toHaveValue('');
});

Ключевые моменты:

  • Использование userEvent.type имитирует ввод текста пользователем.
  • userEvent.click позволяет протестировать реакцию кнопки reset.
  • Проверка состояния через toHaveValue гарантирует, что поля очищены корректно.

Тестирование нативного <form reset>

Если форма использует стандартный HTML-метод сброса, reset можно вызывать через атрибут type="reset" на кнопке:

<form>
  <input placeholder="Username" />
  <input placeholder="Password" type="password" />
  <button type="reset">Reset</button>
</form>

В RTL это проверяется следующим образом:

test('reset через нативную кнопку', async () => {
  render(<LoginForm />);
  const username = screen.getByPlaceholderText(/username/i);
  const password = screen.getByPlaceholderText(/password/i);
  const resetButton = screen.getByRole('button', { name: /reset/i });

  await userEvent.type(username, 'admin');
  await userEvent.type(password, 'qwerty');

  await userEvent.click(resetButton);

  expect(username).toHaveValue('');
  expect(password).toHaveValue('');
});

При этом важно помнить, что нативная кнопка reset работает только для контролируемых HTML-форм без программного управления состоянием через useState. Если состояние управляется React, кнопка должна быть привязана к функции сброса состояния.


Очистка форм с внешними библиотеками управления состоянием

Часто формы создаются через библиотеки типа React Hook Form или Formik. В таких случаях есть собственные методы сброса (reset()):

import { useForm } from 'react-hook-form';

function SignupForm() {
  const { register, handleSubmit, reset } = useForm();

  return (
    <form>
      <input {...register('username')} placeholder="Username" />
      <input {...register('email')} placeholder="Email" />
      <button type="button" onCl ick={() => reset()}>Reset</button>
    </form>
  );
}

Тестирование:

test('React Hook Form reset', async () => {
  render(<SignupForm />);
  const username = screen.getByPlaceholderText(/username/i);
  const email = screen.getByPlaceholderText(/email/i);
  const resetButton = screen.getByRole('button', { name: /reset/i });

  await userEvent.type(username, 'user123');
  await userEvent.type(email, 'user@example.com');

  await userEvent.click(resetButton);

  expect(username).toHaveValue('');
  expect(email).toHaveValue('');
});

Особенности:

  • Метод reset() библиотеки восстанавливает все поля сразу, включая значения по умолчанию.
  • Для тестов важно проверять именно отображаемые значения, а не внутреннее состояние формы.

Проверка побочных эффектов при сбросе

Форма может иметь валидацию, состояния ошибок или side-effects при очистке. Пример проверки:

test('очистка формы сбрасывает ошибки', async () => {
  render(<LoginForm />);
  const emailInput = screen.getByPlaceholderText(/email/i);
  const resetButton = screen.getByRole('button', { name: /reset/i });

  await userEvent.type(emailInput, 'invalid-email');
  await userEvent.click(screen.getByRole('button', { name: /submit/i }));

  expect(screen.getByText(/invalid email/i)).toBeInTheDocument();

  await userEvent.click(resetButton);

  expect(emailInput).toHaveValue('');
  expect(screen.queryByText(/invalid email/i)).not.toBeInTheDocument();
});

Таким образом, проверяется не только очистка полей, но и удаление сообщений об ошибках, что критично для корректного поведения UX.


Советы по написанию тестов для reset

  1. Всегда использовать userEvent вместо fireEvent для имитации реальных действий пользователя.
  2. Проверять только видимое состояние формы, а не внутренние значения состояния, чтобы тесты оставались надёжными при рефакторинге.
  3. Тестировать формы как контролируемые компоненты и с использованием библиотек управления формами, чтобы охватывать разные сценарии.
  4. Не забывать про проверку побочных эффектов: ошибки, disabled-кнопки, отображаемые сообщения.
  5. Если форма сложная, разбивать тесты на отдельные сценарии: ввод данных, сброс, отправка, ошибки.

Тщательное тестирование функциональности reset обеспечивает уверенность в стабильной работе формы и предотвращает неожиданные сбои при взаимодействии пользователя. Такой подход соответствует принципам RTL: проверять интерфейс так, как его видит и использует реальный пользователь.