ViraStack
  • Akademi
  • Hakkında
  • Destek
Genel BakışKaynaklarKatkı
ViraStack

Frontend geliştiricileri için DX odaklı araç seti: starter’lar, ajan kuralları, form hook’ları ve rehber.

Ürünler

  • ViraStack Start
  • ViraStack AI
  • ViraStack Mask
  • ViraStack Password
  • ViraStack Guide

Keşfet

  • Akademi
  • Hakkında
  • Marka
  • Destek

© 2026 ViraStack.

Açık kaynak · MIT

Vitest

06 / 12

Kullanıcı Etkileşimi

userEvent, arama kutusu ve kupon formu.

userEvent kullanın

Kullanıcı hareketlerini simüle etmek için iki araç vardır.

AraçNe yaparNe zaman kullanılır
fireEventTek bir DOM olayı gönderir.1Nadiren. userEvent desteklemeyen olaylar için.1
userEventGerçek kullanıcının olay zincirini simüle eder. Eleman görünür ve etkileşilebilir mi diye kontrol eder.1Varsayılan tercih.

userEvent kurulumu

Her testte userEvent.setup() çağırın. Bu çağrıyı render çağrısından önce yapın.1 Dönen user nesnesini kullanın:2

const user = userEvent.setup();
render(<CouponForm onApply={vi.fn()} />);

await user.click(screen.getByRole("button", { name: "Uygula" }));

Tüm user.* metotları Promise döndürür. Her çağrıya await yazın.3

En sık kullanılan metotlar

MetotNe yapar
user.click(el)Elemana tıklar. Checkbox ve radio için de kullanılır.3
user.type(el, "metin")Elemana tıklar ve karakter karakter yazar.4
user.clear(el)Alanın içeriğini siler.4
user.selectOptions(el, "değer")<select> içinden seçenek seçer.4
user.tab()Odağı sonraki elemana taşır.3
user.keyboard("{Enter}")Odaktaki elemana klavye tuşu gönderir.5

  • user.dblClick(el): çift tıklama. user.hover(el) ve user.unhover(el): işaretçiyi elemanın üzerine getirir veya çeker.3

  • user.upload(el, file): dosya yükler.4 user.paste("metin"): odaktaki elemana metin yapıştırır.6

  • user.type mevcut metni silmez. Yeni karakterleri sona ekler. Dolu bir alanı değiştirmek için önce user.clear çağırın.4

Arama kutusu testi

Kullanıcı arama kutusuna yazdığında liste filtrelenir. Alanı temizleyince tüm ürünler geri gelir:

test("aramaya göre ürünleri filtreler", async () => {
  const user = userEvent.setup();
  render(
    <ProductList
      loadProducts={() => Promise.resolve(products)}
      onAddToCart={vi.fn()}
    />,
  );

  const search = await screen.findByLabelText("Ürün ara");
  await user.type(search, "klavye");

  expect(screen.getAllByRole("article")).toHaveLength(1);
  expect(
    screen.queryByRole("heading", { name: "Oyuncu Faresi" }),
  ).not.toBeInTheDocument();

  await user.clear(search);

  expect(screen.getAllByRole("article")).toHaveLength(3);
});

Klavye erişimi testi

Kullanıcıların bir bölümü fare kullanmaz.7 Aynı işlemi klavye ile yapın. İlk Tab arama kutusuna, ikinci Tab ilk ürünün butonuna gider:3

test("klavye ile ürünü sepete ekler", async () => {
  const user = userEvent.setup();
  const onAddToCart = vi.fn();
  render(
    <ProductList
      loadProducts={() => Promise.resolve(products)}
      onAddToCart={onAddToCart}
    />,
  );

  await screen.findAllByRole("article");
  await user.tab();
  await user.tab();
  await user.keyboard("{Enter}");

  expect(onAddToCart).toHaveBeenCalledWith(products[0]);
});

Form testi: kupon kodu

Sepet sayfasında kullanıcı bir kupon kodu girer. Boş kod hata verir. Kod, baştaki ve sondaki boşluklar silinerek gönderilir.

import { useState, type FormEvent } from "react";

type CouponFormProps = {
  onApply: (code: string) => void;
};

export function CouponForm({ onApply }: CouponFormProps) {
  const [code, setCode] = useState("");
  const [error, setError] = useState<string | null>(null);

  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();

    const trimmedCode = code.trim();

    if (!trimmedCode) {
      setError("Kupon kodu zorunludur.");
      return;
    }

    setError(null);
    onApply(trimmedCode);
  }

  return (
    <form onSubmit={handleSubmit} noValidate>
      <label htmlFor="coupon">Kupon kodu</label>
      <input
        id="coupon"
        value={code}
        onChange={(event) => setCode(event.target.value)}
        aria-invalid={error ? true : undefined}
        aria-describedby={error ? "coupon-error" : undefined}
      />
      {error ? (
        <p id="coupon-error" role="alert">
          {error}
        </p>
      ) : null}
      <button type="submit">Uygula</button>
    </form>
  );
}

Başarılı gönderimi tıklama ve Enter ile test edin:

test("kullanıcı kodu yazıp gönderir", async () => {
  const user = userEvent.setup();
  const onApply = vi.fn();
  render(<CouponForm onApply={onApply} />);

  await user.type(screen.getByLabelText("Kupon kodu"), "YAZ2026");
  await user.click(screen.getByRole("button", { name: "Uygula" }));

  expect(onApply).toHaveBeenCalledWith("YAZ2026");
});

test("Enter tuşu formu gönderir", async () => {
  const user = userEvent.setup();
  const onApply = vi.fn();
  render(<CouponForm onApply={onApply} />);

  await user.type(screen.getByLabelText("Kupon kodu"), "YAZ2026{Enter}");

  expect(onApply).toHaveBeenCalledWith("YAZ2026");
});

İpucu

Bir metin alanında Enter, formdaki ilk gönder butonuna tıklar. Bu buton devre dışıysa form gönderilmez. Formda gönder butonu yoksa Enter yalnızca formda tek bir metin alanı varsa gönderir. Bu davranış HTML standardında tanımlıdır.8

Hata durumunu ve hatanın kaybolmasını test edin:

test("boş kod için hata gösterir ve göndermez", async () => {
  const user = userEvent.setup();
  const onApply = vi.fn();
  render(<CouponForm onApply={onApply} />);

  await user.click(screen.getByRole("button", { name: "Uygula" }));

  expect(screen.getByRole("alert")).toHaveTextContent("Kupon kodu zorunludur.");
  expect(screen.getByLabelText("Kupon kodu")).toBeInvalid();
  expect(onApply).not.toHaveBeenCalled();
});

test("geçerli kod girilince hata kaybolur", async () => {
  const user = userEvent.setup();
  render(<CouponForm onApply={vi.fn()} />);

  await user.click(screen.getByRole("button", { name: "Uygula" }));
  await user.type(screen.getByLabelText("Kupon kodu"), "YAZ2026");
  await user.click(screen.getByRole("button", { name: "Uygula" }));

  expect(screen.queryByRole("alert")).not.toBeInTheDocument();
});

toBeInvalid(), aria-invalid="true" olan veya checkValidity() sonucu false olan elemanı geçersiz sayar. Bu alanda required gibi bir kısıt olmadığı için checkValidity() true döner, test aria-invalid sayesinde geçer.9

Sonucu kontrol edin

Kullanıcının gördüğü sonucu kontrol edin.10

Yap

  • expect(screen.getByRole("alert")).toHaveTextContent(...)9

  • expect(input).toHaveValue("abc")9

  • expect(button).toBeDisabled()9

Yapma

  • Component'in iç state değerini okumak.10

  • isDisabled gibi iç değişkenlere bakmak.10

Kendin dene

CouponForm boşlukları siler. Bunu test edin:

  • Kullanıcı " YAZ2026 " yazar ve gönderir.

  • onApply fonksiyonu "YAZ2026" ile çağrılır.

Kaynaklar

  • 1Dokümanuser-event: Introduction

    `fireEvent` ile farkı, `userEvent.setup()` ve `render` sırası.

  • 2Dokümanuser-event: Setup

    `setup()` ile oluşturulan örnek ve doğrudan API'lerle farkı.

  • 3Dokümanuser-event: Convenience APIs

    `click`, `dblClick`, `hover`, `unhover` ve `tab`.

  • 4Dokümanuser-event: Utility APIs

    `type`, `clear`, `selectOptions` ve `upload`.

  • 5Dokümanuser-event: Keyboard

    `keyboard` ve tuş sözdizimi.

  • 6Dokümanuser-event: Clipboard

    `paste`.

  • 7DokümanW3C: Understanding SC 2.1.1 Keyboard

    Tüm işlevlerin klavyeyle yapılabilmesi ve bunun kimlere fayda sağladığı.

  • 8DokümanHTML Standard: Implicit submission

    `Enter` ile örtük form gönderimi kuralları.

  • 9Repojest-dom

    `toBeInvalid`, `toHaveTextContent`, `toHaveValue` ve `toBeDisabled`.

  • 10MakaleKent C. Dodds: Testing Implementation Details

    Kullanıcının görmediği iç state'i test etmenin sorunları.

Bu sayfayı düzenle

Son güncelleme tarihi: 14 Ekim 2026

Önceki adım05. SorgularSonraki adım07. Asenkron Testler