Mundarija (38)
- Bu darsda
- 1. Nega bu kerak?
- 2. fireEvent va user-event
- 2.1 Bitta bosish — nechta hodisa?
- 2.2 Foydalanuvchi qila olmaydigan narsa
- 3. userEvent.setup() va asosiy amallar
- 3.1 Sozlash
- 3.2 Amallar jadvali
- 3.3 type va keyboard: maxsus tugmalar
- 4. Forma testi
- 4.1 Foydalanuvchi ssenariysi
- 4.2 Nima tekshiriladi
- 5. Async UI: findBy, waitFor, waitForElementToBeRemoved
- 5.1 Uchta kutuvchi
- 5.2 Sekin saqlash
- 5.3 Tuzoq: element bor, matn hali yo'q
- 5.4 waitFor qoidalari
- 6. act ogohlantirishi
- 6.1 Xabar
- 6.2 Biz topgan jimlik: globals: false da ogohlantirish yo'q
- 6.3 Suspense va await act
- 7. Soxta soat va user-event
- 8. Vazifalar qadami: user-event va async (holat 05)
- 8.1 O'rnatish
- 8.2 harakatlar.test.tsx (5 test)
- 8.3 Til tanlash va qisman vi.mock
- 8.4 Soxta server — hozircha qo'lda
- 8.5 Natija — va birinchi beqarorlik
- Qo'shimcha qadam: act muhiti
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): miqdor tanlagich
- 3-mashq (qiyin): kechikkan qidiruv
- 4-mashq: Vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
React Testing Library II: user-event, findBy, waitFor va forma testlari
Qisqacha:
fireEventbitta hodisa yuboradi, foydalanuvchi esa bitta bosishda oltita hodisa hosil qiladi (pointerdown→mousedown→ fokus →pointerup→mouseup→click). user-event shuni takrorlaydi:const user = userEvent.setup(), keyinawait user.click(…),await user.type(maydon, "Osh{Enter}"),await user.tab(). U foydalanuvchi qila olmaydigan narsani ham qilmaydi — o'chirilgan maydonga yozmaydi. Keyin paydo bo'ladigan natijanifindBy…yokiwaitForkutadi.actogohlantirishi — "yangilanish testdan tashqarida bo'ldi" degani; uni yashirmang, sababini toping.
Bu darsda
fireEventvauser-eventfarqini hodisalar ketma-ketligida ko'rasiz.setup,click,type,clear,keyboard,tab,selectOptionsbilan ishlaysiz.- Formani foydalanuvchi kabi to'ldirib, xato va yuborishni tekshirasiz.
findBy,waitForvawaitForElementToBeRemovedbilan async UI'ni kutasiz; ularning tuzoqlarini bilasiz.actogohlantirishi va soxta soat bilan user-event qachon "qotib qolishini" tushuntirasiz.
Oldin bilishingiz kerak: React Testing Library I, Vitest II, Hodisalarni boshqarish.
1. Nega bu kerak?
Hodisalarni boshqarish darsida aytgan edik: testlar hodisani fireEvent yoki user-event bilan chaqiradi, foydalanuvchiga yaqinrog'i — user-event. Endi nega ekanini ko'ramiz.
Bobur sichqonchani ishlatmaydi — faqat klaviatura. U «Saqlash» tugmasiga Tab bilan boradi va Enter bosadi. Sardorning testi esa fireEvent.click(tugma) qiladi. Tugma <div onClick> bo'lsa ham, tabIndex siz bo'lsa ham — test o'tadi. Bobur esa tugmaga yetib bora olmaydi.
Muammo shunda: fireEvent — hodisani yuborish, foydalanuvchi harakati emas. U "bosildi" deydi, lekin fokus o'zgarmaydi, mousedown bo'lmaydi, element o'chirilganmi yoki ustini boshqa narsa yopganmi — tekshirmaydi.
O'xshatish: oshxonaga buyurtma yozilgan qog'ozni to'g'ridan oshpazning qo'liga berish (fireEvent) va mijozning eshikdan kirib, navbatda turib, kassaga aytishi (user-event). Birinchisida eshik yopiq bo'lsa ham buyurtma "keldi".
2. fireEvent va user-event
2.1 Bitta bosish — nechta hodisa?
Tugmaga oltita tinglovchi qo'yamiz va ikkala usulda bosamiz:
import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { test } from "vitest";
const events = [];
function AddButton() {
const log = (e) => events.push(e.type);
return (
<button
onPointerDown={log}
onMouseDown={log}
onFocus={log}
onPointerUp={log}
onMouseUp={log}
onClick={log}
>
Qo'shish
</button>
);
}
test("fireEvent.click", () => {
render(<AddButton />);
events.length = 0;
fireEvent.click(screen.getByRole("button", { name: "Qo'shish" }));
console.log(events.join(" → "));
});
test("user.click", async () => {
const user = userEvent.setup();
render(<AddButton />);
events.length = 0;
await user.click(screen.getByRole("button", { name: "Qo'shish" }));
console.log(events.join(" → "));
});Konsolda:
click
✓ fireEvent.click
pointerdown → mousedown → focus → pointerup → mouseup → click
✓ user.click
Testlar: 2 o'tdi (jami 2)fireEvent.click — faqat click. user.click — brauzerdagi to'liq tartib, o'rtada fokus ham bor. Komponent onMouseDown da menyuni ochsa yoki onFocus da maslahat ko'rsatsa — fireEvent.click bilan test buni hech qachon ko'rmaydi. Haqiqiy Vitest + jsdom'da ham xuddi shu ikki qator chiqadi (biz sinadik).
2.2 Foydalanuvchi qila olmaydigan narsa
Ikkinchi farq muhimroq. O'chirilgan (disabled) maydonga foydalanuvchi yoza olmaydi. Ustida pointer-events: none bo'lgan tugmani bosa olmaydi:
import { fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { expect, test } from "vitest";
function Closed() {
return (
<form>
<label>
Izoh
<input
disabled
onChange={(e) => console.log("onChange:", e.target.value)}
/>
</label>
</form>
);
}
test("disabled maydon — fireEvent baribir yozadi", () => {
render(<Closed />);
const note = screen.getByRole("textbox", { name: "Izoh" });
fireEvent.change(note, { target: { value: "Achchiq" } });
console.log("qiymat:", note.value);
});
test("disabled maydon — user.type yozmaydi", async () => {
const user = userEvent.setup();
render(<Closed />);
const note = screen.getByRole("textbox", { name: "Izoh" });
await user.type(note, "Achchiq");
expect(note).toHaveValue("");
});
test("ustidan yopilgan tugma", async () => {
const user = userEvent.setup();
render(<button style={{ pointerEvents: "none" }}>Yopish</button>);
await user.click(screen.getByRole("button", { name: "Yopish" }));
});Konsolda:
onChange: Achchiq
qiymat: Achchiq
✓ disabled maydon — fireEvent baribir yozadi
✓ disabled maydon — user.type yozmaydi
× ustidan yopilgan tugma
→ Unable to perform pointer interaction as the element has `pointer-events: none`:
BUTTON(label=Yopish)
Testlar: 1 yiqildi, 2 o'tdi (jami 3)Birinchi test — yolg'on yashilning tayyor manbai (Nega test yozamiz): o'chirilgan maydon onChange ni chaqirdi, komponent "yozildi" deb o'yladi. Uchinchi testdagi xabar: "Element pointer-events: none bo'lgani uchun sichqoncha harakatini bajarib bo'lmaydi". Bu — yaxshi xato: ustiga ko'rinmas qatlam tushib qolgan tugmani foydalanuvchi ham bosa olmaydi.
Tekshirib ko'ring:
<div onClick={save}>Saqlash</div>nifireEvent.clickvauser.clickbilan bosish mumkinmi?user.tab()bilan unga yetib borish-chi?
Javob
Ikkalasi ham bosadi — div ga sichqoncha bilan bosish mumkin. Lekin user.tab() unga yetib bormaydi: div fokus olmaydi (Klaviatura navigatsiyasi). Bobur ham yetib bormaydi. Avval getByRole("button", { name: "Saqlash" }) yiqiladi — div ning roli yo'q. To'g'risi — <button>.
3. userEvent.setup() va asosiy amallar
3.1 Sozlash
const user = userEvent.setup(); // har test boshida
await user.click(tugma); // har amal — Promisesetup()testda,renderdan oldin yoki keyin — har test o'zuseri bilan. Ichida klaviatura holati (bosib turilganShift), sichqoncha holati bor; testlar orasida aralashmasin.- Har amal
awaitbilan. user-event amallari asinxron: harflar orasida (sukutdelay: 0) navbatni bo'shatadi.awaitsiz keyingi qator yozish tugamasdan ishlaydi. - Eski kodda
userEvent.click(tugma)(to'g'ridan,setupsiz) ko'rasiz — 13-versiyadan qolgan uslub, ishlaydi, lekin har chaqiruv yangi holat bilan boshlanadi. Hujjatsetup()ni tavsiya qiladi.
3.2 Amallar jadvali
| Amal | Nima qiladi |
|---|---|
click(el), dblClick(el) |
sichqoncha bilan bosish (to'liq tartib) |
hover(el), unhover(el) |
ustiga olib borish / olib ketish |
type(el, "matn") |
avval bosadi, keyin har harf uchun klaviatura hodisalari |
clear(el) |
maydonni tozalaydi (tanlab, o'chirib) |
keyboard("{Enter}") |
fokusdagi elementga tugmalar |
tab(), tab({ shift: true }) |
keyingi / oldingi fokuslanadigan element |
selectOptions(select, "Manti") |
<select> da variant tanlash |
upload(input, fayl) |
<input type="file"> ga fayl |
paste("matn") |
buferdan qo'yish |
3.3 type va keyboard: maxsus tugmalar
type va keyboard matnida jingalak qavs — tugma nomi:
| Yozuv | Ma'nosi |
|---|---|
{Enter}, {Escape}, {Tab} |
bitta tugma |
{Backspace}, {Delete} |
o'chirish |
{ArrowDown}, {Home}, {End} |
strelka va surish |
{Shift>}a{/Shift} |
Shift ni bosib turib a |
{{ |
oddiy { belgisi |
Qidiruv qutisida hammasi birga:
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { expect, test, vi } from "vitest";
function SearchBox({ onSearch }) {
const [query, setQuery] = useState("");
function submit(e) {
e.preventDefault();
onSearch(query.trim());
}
return (
<form role="search" onSubmit={submit}>
<label htmlFor="q">Taom qidirish</label>
<input
id="q"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button type="button" onClick={() => setQuery("")}>
Tozalash
</button>
<button>Qidirish</button>
</form>
);
}
test("yozish, xatoni o'chirish va Enter", async () => {
const user = userEvent.setup();
const onSearch = vi.fn();
render(<SearchBox onSearch={onSearch} />);
const box = screen.getByRole("textbox", { name: "Taom qidirish" });
await user.type(box, "lag'monn{Backspace}{Enter}");
expect(onSearch).toHaveBeenCalledExactlyOnceWith("lag'mon");
await user.clear(box);
expect(box).toHaveValue("");
});
test("faqat klaviatura: Tab va Enter", async () => {
const user = userEvent.setup();
render(<SearchBox onSearch={vi.fn()} />);
const box = screen.getByRole("textbox", { name: "Taom qidirish" });
await user.tab();
expect(box).toHaveFocus();
await user.keyboard("Osh");
await user.tab();
const clear = screen.getByRole("button", { name: "Tozalash" });
expect(clear).toHaveFocus();
await user.keyboard("{Enter}");
expect(box).toHaveValue("");
console.log(document.activeElement.textContent);
});Konsolda:
✓ yozish, xatoni o'chirish va Enter
Tozalash
✓ faqat klaviatura: Tab va Enter
Testlar: 2 o'tdi (jami 2)Uch narsaga e'tibor bering:
{Enter}maydonda — forma yuborildi (onSubmitchaqirildi). Brauzerdagi "yashirin yuborish" (implicit submission) — user-event uni ham takrorlaydi.user.keyboard("Osh")—typedan farqli, hech narsani bosmaydi: fokus qayerda bo'lsa, o'sha yerga yozadi. Bobur shunday ishlaydi.Entertugmada —click. Ikkinchi test Bobur uchun to'liq yo'lni tekshirdi: maydonga yetib borish, yozish, keyingi tugmaga o'tish, bosish.
Tekshirib ko'ring: «Tozalash» tugmasidan
type="button"ni olib tashlasangiz, ikkinchi test nima qiladi?
Javob
Formadagi type siz tugma — submit. Enter uni bosadi va forma yuboriladi, lekin onClick ham ishlaydi — maydon baribir tozalanadi. Test o'tadi, onSearch("Osh") esa jim chaqiriladi. Buni ushlash uchun onSearch ga expect(onSearch).not.toHaveBeenCalled() qo'shing — "nima bo'lmasligi kerak" ham tekshiruv.
4. Forma testi
4.1 Foydalanuvchi ssenariysi
Forma testi — foydalanuvchining ikki yo'li: noto'g'ri to'ldirish (xato ko'rinadi, yuborilmaydi) va to'g'ri to'ldirish (ma'lumot ketadi). «Bahor» oshxonasining yetkazib berish formasi:
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useRef, useState } from "react";
import { expect, test, vi } from "vitest";
function OrderForm({ onOrder }) {
const [error, setError] = useState("");
const nameRef = useRef(null);
function submit(e) {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget));
if (!data.name.trim()) {
setError("Ismingizni yozing");
nameRef.current.focus();
return;
}
setError("");
onOrder(data);
}
return (
<form aria-label="Buyurtma" noValidate onSubmit={submit}>
<label htmlFor="name">Ism</label>
<input
id="name"
name="name"
ref={nameRef}
aria-invalid={error ? "true" : "false"}
aria-describedby="name-error"
/>
<p id="name-error" role="alert">{error}</p>
<label htmlFor="dish">Taom</label>
<select id="dish" name="dish" defaultValue="Osh">
<option>Osh</option>
<option>Lag'mon</option>
<option>Manti</option>
</select>
<button>Buyurtma berish</button>
</form>
);
}
test("ism bo'sh — xato, fokus maydonda, yuborilmaydi", async () => {
const user = userEvent.setup();
const onOrder = vi.fn();
render(<OrderForm onOrder={onOrder} />);
await user.click(
screen.getByRole("button", { name: "Buyurtma berish" }),
);
const name = screen.getByRole("textbox", { name: "Ism" });
expect(screen.getByRole("alert")).toHaveTextContent(
"Ismingizni yozing",
);
expect(name).toBeInvalid();
expect(name).toHaveAccessibleDescription("Ismingizni yozing");
expect(name).toHaveFocus();
expect(onOrder).not.toHaveBeenCalled();
});
test("to'g'ri to'ldirilgan — ma'lumot ketadi", async () => {
const user = userEvent.setup();
const onOrder = vi.fn();
render(<OrderForm onOrder={onOrder} />);
await user.type(screen.getByLabelText("Ism"), "Dilshod");
const dish = screen.getByRole("combobox", { name: "Taom" });
await user.selectOptions(dish, "Manti");
await user.click(
screen.getByRole("button", { name: "Buyurtma berish" }),
);
expect(dish).toHaveDisplayValue("Manti");
expect(onOrder).toHaveBeenCalledExactlyOnceWith({
name: "Dilshod",
dish: "Manti",
});
console.log(onOrder.mock.calls[0][0]);
});Konsolda:
✓ ism bo'sh — xato, fokus maydonda, yuborilmaydi
{name: 'Dilshod', dish: 'Manti'}
✓ to'g'ri to'ldirilgan — ma'lumot ketadi
Testlar: 2 o'tdi (jami 2)Test komponentning ichini bilmaydi: useState bormi, FormData mi yoki React Hook Form mi — farqi yo'q. U faqat foydalanuvchi ko'radiganini tekshiradi: xabar, aria-invalid, fokus, va tashqariga nima chiqdi (onOrder chaqiruvi). Ertaga formani RHF'ga ko'chirsangiz, test o'zgarmaydi — shu uning qiymati.
4.2 Nima tekshiriladi
toHaveAccessibleDescription— xato matni maydongaaria-describedbybilan ulangan. Malika maydonga kelganda ekran o'quvchi xatoni o'qiydi. Matn ekranda bor, lekin ulanmagan bo'lsa — bu tekshiruv yiqiladi.noValidate— brauzerning o'z tekshiruvi (requiredpufakchasi) o'chirilgan, xatoni komponent ko'rsatadi.requiredqoldirsangiz, Chrome'da forma umuman yuborilmaydi va test boshqa narsani tekshiradi.- Tugmani bosish,
fireEvent.submit(forma)emas.submithodisasi tugmadisabledbo'lsa ham, tugma umuman bo'lmasa ham ishlaydi. Foydalanuvchi esa tugmani bosadi yokiEnterni.
RHF formasi darsida va'da qilgan edik: RHF formasi ham oddiy foydalanuvchidek sinaladi — maydonga yozish, tugma bosish, keyin findByText("Ismingizni yozing"). Nega aynan findBy — keyingi bo'limda: RHF tekshiruvi asinxron, xato bir lahzadan keyin paydo bo'ladi.
5. Async UI: findBy, waitFor, waitForElementToBeRemoved
5.1 Uchta kutuvchi
RTL I da findBy ni ko'rdik: element paydo bo'lguncha kutadi. Endi to'liq rasm:
| Vosita | Nimani kutadi | Sukut |
|---|---|---|
await screen.findByRole(…) |
element paydo bo'lishini | 1000 ms, har 50 ms |
await waitFor(() => expect(…)) |
ichidagi tekshiruv o'tishini | 1000 ms, har 50 ms |
await waitForElementToBeRemoved(el) |
element yo'qolishini | 1000 ms |
findBy… = waitFor + getBy…. Element topilishi kerak bo'lsa — findBy. Boshqa narsa (matn, atribut, soxta funksiya chaqiruvi) kerak bo'lsa — waitFor.
sequenceDiagram
participant T as test
participant W as waitFor
participant D as DOM
T->>W: waitFor(tekshiruv)
loop har 50 ms (yoki DOM o'zgarganda)
W->>D: tekshiruvni ishga tushir
D-->>W: xato — yana kutamiz
end
W->>D: tekshiruvni ishga tushir
D-->>W: o'tdi
W-->>T: tayyor (1 s o'tsa — oxirgi xato)waitFor faqat taymer bilan emas, DOM o'zgarishi bilan ham qayta urinadi (MutationObserver) — shuning uchun javob kelishi bilan darhol tugaydi.
5.2 Sekin saqlash
Server o'rnida 200 ms dan keyin javob beradigan soxta funksiya:
import {
render,
screen,
waitFor,
waitForElementToBeRemoved,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { expect, test, vi } from "vitest";
function SaveDish({ save }) {
const [status, setStatus] = useState("idle");
async function handleClick() {
setStatus("saving");
await save("Osh");
setStatus("saved");
}
return (
<div>
<button disabled={status === "saving"} onClick={handleClick}>
Saqlash
</button>
{status === "saving" && <p role="status">Saqlanmoqda…</p>}
{status === "saved" && <p>Saqlandi</p>}
</div>
);
}
const slowSave = () =>
new Promise((resolve) => setTimeout(resolve, 200));
test("tugma o'chadi, keyin Saqlandi", async () => {
const user = userEvent.setup();
const save = vi.fn(slowSave);
render(<SaveDish save={save} />);
const button = screen.getByRole("button", { name: "Saqlash" });
await user.click(button);
expect(button).toBeDisabled();
expect(await screen.findByText("Saqlandi")).toBeInTheDocument();
expect(button).toBeEnabled();
expect(save).toHaveBeenCalledExactlyOnceWith("Osh");
});
test("yuklanish yozuvi yo'qoladi", async () => {
const user = userEvent.setup();
render(<SaveDish save={slowSave} />);
await user.click(screen.getByRole("button", { name: "Saqlash" }));
await waitForElementToBeRemoved(screen.getByRole("status"));
const gone = screen.queryByRole("status") === null;
console.log("status yo'qoldi:", gone);
});
test("waitFor — soxta funksiya chaqiruvini kutish", async () => {
const user = userEvent.setup();
const save = vi.fn(slowSave);
render(<SaveDish save={save} />);
const button = screen.getByRole("button", { name: "Saqlash" });
await user.dblClick(button);
await waitFor(() =>
expect(screen.getByText("Saqlandi")).toBeVisible(),
);
expect(save).toHaveBeenCalledTimes(1);
});Konsolda:
✓ tugma o'chadi, keyin Saqlandi
status yo'qoldi: true
✓ yuklanish yozuvi yo'qoladi
✓ waitFor — soxta funksiya chaqiruvini kutish
Testlar: 3 o'tdi (jami 3)Uchinchi test kichik bir xatoni ham tekshirdi: ikki marta bosish (dblClick) — so'rov bitta. Birinchi bosishdan keyin tugma o'chdi, ikkinchisi o'tmadi. Sabrsiz mijozlar shunday qiladi — test ularning yo'lini ham sinadi.
waitForElementToBeRemoved ga element hozir bor bo'lishi kerak: chaqirilganda u yo'q bo'lsa, darhol xato beradi ("The element(s) given to waitForElementToBeRemoved are already removed").
5.3 Tuzoq: element bor, matn hali yo'q
Ko'p uchraydigan holat — xato yozuvi uchun joy doim bor, faqat ichi bo'sh (aria-live hududlari shunday qilinadi: ekran o'quvchi o'zgarishni sezishi uchun hudud oldindan turadi):
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { expect, test } from "vitest";
function EditDish({ save }) {
const [error, setError] = useState("");
async function submit(e) {
e.preventDefault();
try {
await save();
} catch (err) {
setError(`Saqlanmadi: ${err.message}`);
}
}
return (
<form onSubmit={submit}>
<button>Saqlash</button>
<p role="alert">{error}</p>
</form>
);
}
const failingSave = () =>
new Promise((_, reject) => {
setTimeout(() => reject(new Error("Ataylab")), 200);
});
test("findByRole — bo'sh alert'ni darhol topadi", async () => {
const user = userEvent.setup();
render(<EditDish save={failingSave} />);
await user.click(screen.getByRole("button", { name: "Saqlash" }));
const alert = await screen.findByRole("alert");
expect(alert).toHaveTextContent("Saqlanmadi: Ataylab");
});
test("waitFor — matnning o'zini kutadi", async () => {
const user = userEvent.setup();
render(<EditDish save={failingSave} />);
await user.click(screen.getByRole("button", { name: "Saqlash" }));
await waitFor(() =>
expect(screen.getByRole("alert")).toHaveTextContent(
"Saqlanmadi: Ataylab",
),
);
});Konsolda:
× findByRole — bo'sh alert'ni darhol topadi
→ expected <p> to have text content 'Saqlanmadi: Ataylab'
Received: ''
✓ waitFor — matnning o'zini kutadi
Testlar: 1 yiqildi, 1 o'tdi (jami 2)Haqiqiy Vitest + jsdom'da birinchi test xuddi shunday yiqiladi, xabari jest-dom'niki:
FAIL sinov/edit.test.jsx > findByRole — bo'sh alert'ni darhol topadi
Error: expect(element).toHaveTextContent()
Expected element to have text content:
Saqlanmadi: Ataylab
Received:
findByRole("alert") hech narsa kutmadi — <p role="alert"> boshidan bor edi. Tekshiruv bo'sh matnni ko'rdi. waitFor esa matn kelguncha qayta urindi. Muqobil: await screen.findByText("Saqlanmadi: Ataylab") — u ham matnni kutadi.
Eng xavflisi — server tez javob bergan holat: bir zumda javob kelsa, birinchi test ham tasodifan o'tib ketishi mumkin. Kanon vazifalar da aynan shunday yashirin xato bor edi — u MSW darsida, soxta server haqiqiy tarmoqdek kechroq javob bera boshlaganda ochildi.
5.4 waitFor qoidalari
| Qilmang | Nega | O'rniga |
|---|---|---|
waitFor(() => user.click(…)) |
ichidagi kod har 50 ms qayta ishlaydi — bir necha marta bosadi | harakat waitFor dan oldin |
waitFor(() => {}) (bo'sh) |
hech narsani kutmaydi, bir marta "nafas oladi" | aniq tekshiruv |
bitta waitFor da 5 ta expect |
birinchisi o'tguncha qolganlari ko'rinmaydi, xato chalkash | waitFor da bittasi, qolgani keyin |
await waitFor(() => getByRole(…)) |
ishlaydi, lekin uzun | await findByRole(…) |
findBy ni await siz |
[object Promise], tekshiruv yo'q |
await |
Sukutdagi 1 s sekin mashinada (CI, coverage bilan) yetmay qolishi mumkin. Bitta testda: waitFor(fn, { timeout: 3000 }). Hammasi uchun — configure({ asyncUtilTimeout: 3000 }) (@testing-library/react dan). Kanon buni Test strategiyasi darsida qiladi — yuk ostidagi o'lchovdan keyin.
6. act ogohlantirishi
6.1 Xabar
RTL I da act ni ko'rdik: ichidagi yangilanishlar tugaguncha kutadi. RTL render, fireEvent, user-event amallari va waitFor ni act ichida o'zi bajaradi. Yangilanish shulardan tashqarida bo'lsa, React ogohlantiradi.
Sabab ko'pincha bitta: test tugash yoki tekshirish oldidan async ishni kutmagan. Mana shunday test (haqiqiy Vitest, globals: true bilan; komponent — «Sekin saqlash» dagi kabi, holat matni <p role="status"> da):
test("saqlandi", async () => {
const user = userEvent.setup();
render(<SaveButton save={save} />); // save — 50 ms
await user.click(screen.getByRole("button", { name: "Saqlash" }));
await new Promise((r) => setTimeout(r, 100)); // oddiy kutish
expect(screen.getByRole("status")).toHaveTextContent("Saqlandi");
});stderr | sinov/saqlash.test.jsx > saqlandi
An update to SaveButton inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at https://react.dev/link/wrap-tests-with-act
✓ sinov/saqlash.test.jsx (1 test) 287msTarjima: "Testda SaveButton yangilanishi act(...) ga o'ralmagan. React holatini o'zgartiradigan kodni act ga o'rang… Shunda brauzerda foydalanuvchi ko'radigan xulqni tekshirasiz."
Test o'tdi — ogohlantirish xato emas. Lekin u aytadi: setStatus("Saqlandi") RTL bilmagan paytda bo'ldi. Bu safar 100 ms yetdi; sekin mashinada yetmaydi. Davosi act ni qo'lda yozish emas — RTL kutuvchisi:
await user.click(screen.getByRole("button", { name: "Saqlash" }));
expect(await screen.findByText("Saqlandi")).toBeInTheDocument();findBy o'zi act ichida kutadi — ogohlantirish yo'qoladi, test ham barqaror.
| Ogohlantirish sababi | Davosi |
|---|---|
| tekshiruv async natijadan oldin | findBy / waitFor |
| test tugadi, javob keyin keldi | oxirgi natijani ham kuting |
holat RTL'dan tashqarida o'zgardi (store, taymer, EventTarget) |
act(() => …) (RTL I) |
| soxta taymer o'tkazildi | act(() => vi.advanceTimersByTime(…)) |
Natija oynasida bu ogohlantirish chiqmaydi (sababi pastda). Uni faqat o'z loyihangiz terminalida ko'rasiz.
6.2 Biz topgan jimlik: globals: false da ogohlantirish yo'q
Yuqoridagi testni ikki marta yurgizdik: globals: true bilan — ogohlantirish chiqdi; kanondagidek globals siz (importlar aniq) — jimlik. Sabab: React ogohlantirishni faqat globalThis.IS_REACT_ACT_ENVIRONMENT === true bo'lsa beradi. Testing Library bu bayroqni o'zi yoqadi — lekin global beforeAll bo'lsa. RTL I dagi cleanup hikoyasining egizagi:
IS_REACT_ACT_ENVIRONMENT: undefinedBu qator — globals siz test ichida console.log(globalThis.IS_REACT_ACT_ENVIRONMENT) natijasi (Vitest 5.0.4, RTL 16.3.3). Sozlash fayliga bitta qator qo'shsak, ogohlantirish qaytdi:
// vitest-sozlash.ts — globals siz Testing Library bu bayroqni
// yoqmaydi; React act ogohlantirishlari shusiz jim
globalThis.IS_REACT_ACT_ENVIRONMENT = true;Kanon vazifalar da globals yo'q va bu qator ham yo'q edi — demak u yerdagi act ogohlantirishlari jim edi. Biz qatorni kanonga qo'shib, o'lchadik (pastdagi «Vazifalar qadami», qo'shimcha qadam):
| Testlar | act ogohlantirishlari |
|
|---|---|---|
| Bayroqsiz (oldin) | 349/349 | 0 — chunki React jim |
| Bayroq bilan (keyin) | 349/349 | 0 — testlar haqiqatan toza |
Ikkinchi qator muhim: bayroq yoqilgach ham 0. Demak 349 test await user.…, findBy… va waitFor ni to'g'ri ishlatgan. O'lchov ishlashini alohida tekshirdik: ataylab act siz yangilanadigan vaqtinchalik test bayroq bilan 1 ta ogohlantirish berdi, bayroqsiz 0. Shuning uchun "0" ga ishonish mumkin.
Bitta istisno: bayroq faqat jsdom testlari uchun. Vitest Browser Mode da xuddi shu qator 6 ta testda 47 ta foydasiz ogohlantirish berdi. Sababi: u yerdagi userEvent haqiqiy brauzer hodisasini yuboradi, act ga o'ralmaydi. Shuning uchun kanon qatorni faqat jsdom sozlash fayliga qo'ydi.
6.3 Suspense va await act
use(): promise va context'ni o'qish darsida haqiqiy tuzoq bor edi: act kutilmagan edi va React dedi: A component suspended inside an act scope, but the act call was not awaited. Qoida: ichida async ish (Promise, Suspense, transition) bo'lsa — await act(async () => { … }). user-event amallari va findBy buni o'zi qiladi; act ni qo'lda faqat RTL'dan tashqaridagi o'zgarish uchun yozasiz.
7. Soxta soat va user-event
Vitest II da soxta taymerlarni ko'rdik va kanonning saboqini aytdik: soatni render tugagach yoqing, aks holda waitFor abadiy kutadi. user-event bilan ham shunday. Komponent 2 soniya kutadi; vaqtni test boshqarmoqchi:
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { afterEach, expect, test, vi } from "vitest";
function SaveButton({ save }) {
const [status, setStatus] = useState("");
async function handleClick() {
await save();
setStatus("Saqlandi");
}
return (
<div>
<button onClick={handleClick}>Saqlash</button>
<p role="status">{status}</p>
</div>
);
}
// Server o'rnida: 2 soniyadan keyin javob
const save = () => new Promise((r) => setTimeout(r, 2000));
afterEach(() => vi.useRealTimers());
test("soxta soat — faqat advanceTimers", async () => {
vi.useFakeTimers();
const user = userEvent.setup({
advanceTimers: vi.advanceTimersByTime,
});
render(<SaveButton save={save} />);
await user.click(screen.getByRole("button", { name: "Saqlash" }));
});
test("soxta soat — shouldAdvanceTime", async () => {
vi.useFakeTimers({ shouldAdvanceTime: true });
const user = userEvent.setup({
advanceTimers: vi.advanceTimersByTime,
});
render(<SaveButton save={save} />);
await user.click(screen.getByRole("button", { name: "Saqlash" }));
await vi.advanceTimersByTimeAsync(2000);
expect(await screen.findByText("Saqlandi")).toBeInTheDocument();
});Konsolda:
× soxta soat — faqat advanceTimers
→ Test timed out in 5000ms.
✓ soxta soat — shouldAdvanceTime
Testlar: 1 yiqildi, 1 o'tdi (jami 2)Natija 5 soniyadan keyin chiqadi — birinchi test shuncha "o'ylaydi".
Haqiqiy Vitest 5.0.4 da ham xuddi shunday (biz ishga tushirdik):
❯ sinov/soat.test.jsx (2 tests | 1 failed) 5064ms
× soxta soat — faqat advanceTimers 5013ms
✓ soxta soat — shouldAdvanceTime 48ms
FAIL sinov/soat.test.jsx > soxta soat — faqat advanceTimers
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".Tarjima: "Test 5000 ms da tugamadi. Uzoq test bo'lsa, oxirgi argument bilan vaqt bering yoki testTimeout ni sozlang." Lekin bu yerda vaqt emas — test qotib qoldi: hech narsa 5 soniya kutmaydi, click ning o'zi tugamadi.
Nega? user-event hujjati advanceTimers ni tavsiya qiladi — u harflar orasidagi kutishni soxta soatga uzatadi. Lekin Testing Library'ning o'zi ham har amaldan keyin setTimeout(…, 0) bilan "nafas oladi". U faqat Jest soxta soatini taniydi. Vitest soatini bilmaydi va to'xtatilgan setTimeout ni abadiy kutadi. shouldAdvanceTime: true — soxta soat haqiqiy vaqt bilan birga yurib turadi: kichik kutishlar o'tib ketadi, katta sakrashni esa advanceTimersByTimeAsync bilan o'zingiz qilasiz. Keyin natijani findByText kutadi — u yangilanishni act ichida kuzatadi.
Ikki test qadamma-qadam:
| Qadam | 1-test (useFakeTimers()) |
2-test (shouldAdvanceTime: true) |
|---|---|---|
user.click boshlandi |
bosish hodisalari yuborildi | bosish hodisalari yuborildi |
RTL setTimeout(…, 0) ni kutadi |
soat to'xtagan — taymer hech qachon ishlamaydi | soat o'zi yuradi — taymer ishlaydi |
| natija | click tugamaydi, 5 s dan keyin yiqiladi |
click tugadi, keyin 2 s ni o'zimiz suramiz |
Amaliy qoida: user-event bilan soxta soat kerak bo'lmasa — ishlatmang. Debounce 300 ms bo'lsa, haqiqiy vaqtda findBy kutadi — test 0.3 s sekinlashadi, xolos. Soxta soat 10 soniyalik, daqiqalik taymerlar uchun.
8. Vazifalar qadami: user-event va async (holat 05)
8.1 O'rnatish
git switch -c test/user-event
npm i -D -E @testing-library/user-event@14.6.7Narx: 1 paket, node_modules ga 1 MB dan kam; ilova bundle'i o'zgarmaydi (JS 206.7 KB gzip) — bu faqat test bog'liqligi. 14.6.7 — kanon tanlagan kunda (2026-10-12 gacha chiqqan) eng yangisi; 14.6.8 keyin chiqdi.
8.2 harakatlar.test.tsx (5 test)
Fayl boshidagi izoh — darsning qisqa xulosasi:
// harakatlar.test.tsx — 22/#05: user-event — foydalanuvchi
// kabi harakat. fireEvent bitta hodisa yuboradi; userEvent.type
// esa har harf uchun keydown → keypress → beforeinput → input →
// keyup, fokus va tanlovni ham o'zgartiradi (brauzerdagidek).
// Har test: const user = userEvent.setup()Birinchi test — vazifa qo'shish:
test("vazifa qo'shish: yozish + Enter — qator, maydon bo'sh va fokusda", async () => {
const user = userEvent.setup();
const sorovlar = await ilovaniOch();
const maydon = screen.getByRole("textbox", {
name: "Yangi vazifa",
});
await user.type(maydon, "Sut olish{Enter}");
// findBy* — paydo bo'lguncha kutadi (sukut: 1 s)
expect(
await screen.findByRole("checkbox", { name: "Sut olish" }),
).toBeInTheDocument();
expect(maydon).toHaveValue("");
expect(maydon).toHaveFocus();
await waitFor(() => expect(sorovlar).toContain("POST "));
});Yangi qator findByRole bilan kutiladi; maydon bo'shagani va fokus maydonda qolgani — keyingi vazifani darhol yozish uchun (Bobur uchun muhim). Oxirgi qator — serverga POST ketganini waitFor bilan kutish.
Qolgan to'rttasi:
| Test | Nima tekshiriladi |
|---|---|
bo'sh matn + Enter |
«Vazifa matnini yozing.» chiqadi, POST yo'q |
| katakcha | bosilishi bilan toBeChecked (optimistik), keyin waitFor — PATCH /1 |
| filtr «Bajarilgan» | toBePressed, bitta qator, location.search — ?filtr=bajarilgan |
TilTanlov |
user.tab() → «O'zbekcha» fokusda, {ArrowDown} → keyingi til |
8.3 Til tanlash va qisman vi.mock
Vitest II da va'da qilgan edik — mana o'sha qisman soxta:
// vi.mock — modulning bitta eksportini almashtirish (qolgani asl).
// Vitest uni fayl boshiga "ko'taradi" (hoisting): importlardan oldin
// ishlaydi. tilniAlmashtir location.assign qiladi — jsdom'da
// navigatsiya yo'q ("Not implemented"), bizga faqat "qaysi til
// bilan chaqirildi" kerak
vi.mock("../src/i18n/index.ts", async (asl) => ({
...(await asl<typeof import("../src/i18n/index.ts")>()),
tilniAlmashtir: vi.fn(),
}));Test esa:
await user.tab();
expect(
screen.getByRole("radio", { name: "O'zbekcha" }),
).toHaveFocus();
await user.keyboard("{ArrowDown}");
expect(vi.mocked(tilniAlmashtir)).toHaveBeenCalledExactlyOnceWith(
"uz-Cyrl",
);Radio guruhda strelka — keyingi variant (ARIA patternlar). fireEvent.keyDown bilan buni sinash uchun brauzer xulqini qo'lda takrorlash kerak edi; user-event uni o'zi qiladi.
8.4 Soxta server — hozircha qo'lda
Server javoblari bu holatda fetch ning vi.fn soxtasi bilan beriladi: metod bo'yicha (GET — ro'yxat, POST — yangi vazifa, PATCH — o'zgargani) va har so'rov ro'yxatga yoziladi ("POST ", "PATCH /1"). Bu — 30 qatorlik qo'l mehnati, URL xatosini ham ko'rmaydi. MSW darsida u tarmoq darajasidagi soxta server bilan almashadi.
O'chirish dialogi va Ctrl+K buyruqlar oynasi bu faylda yo'q: jsdom'da showModal yo'q (RTL I). Ular — Browser Mode va Playwright da.
8.5 Natija — va birinchi beqarorlik
Testlar 337/337 (332 + 5), lint, format:check, tip, knip — toza; JS 206.7 KB. Lekin test:qamrov (coverage bilan) 5 urinishdan 3 tasida 1–6 komponent testi yiqildi:
FAIL |komponent| tekshiruv/harakatlar.test.tsx > foydalanuvchi harakatlari (user-event) > vazifa qo'shish: yozish + Enter — qator, maydon bo'sh va fokusda
Test Files 1 failed | 52 passed (53)
Tests 1 failed | 336 passed (337)Har safar boshqa test (Menyu, Auth, VazifaTahriri …), sabab — Test timed out in 5000ms. O'sha paytda kompyuterda og'ir jarayon (video render) ishlayotgan edi: Duration 10 s dan 32 s gacha o'sdi. Coverage'siz npm test — hammasi o'tdi. Shuning uchun bu holatda qamrov raqami ham yo'q (yiqilgan yurgizish hisobot bermaydi); keyingi holatda u 62.76 % (satr).
Bu — flaky (beqaror) testlarning birinchi belgisi: kod o'zgarmagan, natija o'zgaradi. Kanon uni yashirmadi: xom natijalar saqlandi, tuzatish — Test strategiyasi darsida, sabablarni o'lchab.
git add -A
git commitCommit (kanon): test: user-event — qo'shish, katak, filtr, bo'sh matn; til tanlash vi.mock bilan.
Qo'shimcha qadam: act muhiti
Bu qadamni kanon qism oxirida, 15-holatdan keyin qo'shdi («act ogohlantirishi» bo'limidagi topilma tufayli). Siz uni hozir qilishingiz mumkin. Branch — test/act-muhiti. tekshiruv/vitest-sozlash.ts ga:
// 22/#05: React'ga "bu test muhiti" deyiladi — act() dan tashqaridagi
// state yangilanishi "not wrapped in act(...)" ogohlantirishini beradi.
// Testing Library buni o'zi faqat global beforeAll bo'lsa (globals: true)
// yoqadi; bizda globals yo'q — aks holda ogohlantirishlar jim qoladi.
// Tur: lib.dom va @types/react'da bu global e'lon qilinmagan
declare global {
var IS_REACT_ACT_ENVIRONMENT: boolean | undefined;
}
globalThis.IS_REACT_ACT_ENVIRONMENT = true;declare global nega kerak: usiz tsc -b TS7017 beradi (bu global hech qayerda e'lon qilinmagan). Natija: 349/349, ogohlantirish 0, qamrov 62.72 % (o'zgarmadi), JS 206.7 KB.
Commit (kanon): test: IS_REACT_ACT_ENVIRONMENT — globals: false da act ogohlantirishlari yoqildi.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| user-event amalidan keyin eski holat | await yo'q |
await user.click(…) |
Unable to perform pointer interaction … pointer-events: none |
ustida qatlam yoki pointer-events |
foydalanuvchi ham bosa olmaydi — UI'ni tuzating |
type yozmadi |
maydon disabled/readOnly |
xulq to'g'ri; fireEvent.change bilan "aylanib o'tmang" |
findByRole o'tdi, matn bo'sh |
element boshidan bor | waitFor + toHaveTextContent yoki findByText |
waitFor ichida bosish bir necha marta |
callback qayta ishlaydi | harakatni waitFor dan chiqaring |
… was not wrapped in act(...) |
async natija kutilmagan | findBy/waitFor; tashqi o'zgarish — act |
| ogohlantirish umuman yo'q | globals siz IS_REACT_ACT_ENVIRONMENT yoqilmagan |
sozlash faylida = true |
soxta soat bilan Test timed out in 5000ms |
RTL Vitest soatini tanimaydi | shouldAdvanceTime: true + advanceTimers |
already removed |
waitForElementToBeRemoved ga yo'q element |
avval element borligini tekshiring |
10. Mashqlar
Mashqlar kurs/mashqlar/22/05-user-event/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- Har test boshida:
const user = userEvent.() - Maydonga yozib, yuborish:
await user.type(maydon, "Osh") - Keyingi fokuslanadigan elementga:
await user.() - Matn paydo bo'lishini kutish:
await (() => expect(el).toHaveTextContent("Tayyor")) - Element yo'qolishini kutish:
await (yuklanish)
Yechim
- setup — har test o'z klaviatura/sichqoncha holati bilan.
- {Enter} — jingalak qavsda tugma nomi; maydonda forma yuboriladi.
- tab —
{ shift: true }bilan orqaga. - waitFor — tekshiruv o'tguncha qayta urinadi (1 s).
- waitForElementToBeRemoved — element chaqiruv paytida bor bo'lishi kerak.
2-mashq (o'rta): miqdor tanlagich
Quantity komponenti: «−» va «+» tugmalari (aria-label="Kamaytirish" / "Ko'paytirish"), o'rtada <output aria-live="polite"> da son (boshi 1, oralig'i 1–5). 1 da «−» o'chirilgan, 5 da «+» o'chirilgan. Test yozing: (1) «+» ni to'rt marta bosing — son 5, «+» o'chirilgan; (2) faqat klaviatura: tab bilan «Ko'paytirish» ga boring (birinchi tab — «Kamaytirish» o'chirilgani uchun uni o'tkazib yuboradi!), Enter ikki marta — son 3.
Ishora: o'chirilgan tugma fokus olmaydi; toHaveTextContent("5").
Yechim
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { expect, test } from "vitest";
function Quantity() {
const [n, setN] = useState(1);
return (
<div role="group" aria-label="Miqdor">
<button
aria-label="Kamaytirish"
disabled={n === 1}
onClick={() => setN(n - 1)}
>
−
</button>
<output aria-live="polite">{n}</output>
<button
aria-label="Ko'paytirish"
disabled={n === 5}
onClick={() => setN(n + 1)}
>
+
</button>
</div>
);
}
test("sichqoncha: 5 dan oshmaydi", async () => {
const user = userEvent.setup();
render(<Quantity />);
const plus = screen.getByRole("button", { name: "Ko'paytirish" });
for (let i = 0; i < 4; i++) await user.click(plus);
expect(screen.getByRole("status")).toHaveTextContent("5");
expect(plus).toBeDisabled();
});
test("klaviatura: Tab va Enter", async () => {
const user = userEvent.setup();
render(<Quantity />);
await user.tab();
const plus = screen.getByRole("button", { name: "Ko'paytirish" });
expect(plus).toHaveFocus();
await user.keyboard("{Enter}{Enter}");
console.log(screen.getByRole("status").textContent);
});Konsolda:
✓ sichqoncha: 5 dan oshmaydi
3
✓ klaviatura: Tab va Enter
Testlar: 2 o'tdi (jami 2)<output> ning yashirin roli — status. Ikkinchi testda birinchi tab to'g'ri «Ko'paytirish» ga tushdi: o'chirilgan «Kamaytirish» fokus olmaydi. fireEvent bilan bu tekshiruvni yozib bo'lmasdi — u fokusni umuman bilmaydi.
3-mashq (qiyin): kechikkan qidiruv
DishSearch maydonga yozilganda search(query) ni chaqiradi (soxta server, 200 ms), javob kelguncha <p role="status">Qidirilmoqda…</p>, keyin <ul aria-label="Natijalar"> da taomlar. Topilmasa — «Hech narsa topilmadi». Uch test: (1) «osh» yozilganda natijada Osh va Qovurma osh, status yo'qolgan (waitForElementToBeRemoved); (2) «pizza» — «Hech narsa topilmadi»; (3) search oxirgi marta to'liq so'z bilan chaqirilgan (toHaveBeenLastCalledWith). Qidiruv Enter bilan emas, har o'zgarishda — demak search har harfga chaqiriladi; eski javob yangisini bosib ketmasligi kerak.
Ishora: har so'rovga raqam bering va faqat oxirgisining javobini qabul qiling (race condition).
Yechim
import {
render,
screen,
waitForElementToBeRemoved,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useRef, useState } from "react";
import { expect, test, vi } from "vitest";
const MENU = ["Osh", "Qovurma osh", "Lag'mon", "Manti"];
const fakeSearch = (q) =>
new Promise((resolve) => {
const found = MENU.filter((d) =>
d.toLowerCase().includes(q.toLowerCase()),
);
setTimeout(() => resolve(found), 200);
});
function DishSearch({ search }) {
const [state, setState] = useState({ status: "idle", items: [] });
const last = useRef(0);
async function change(e) {
const id = ++last.current;
setState({ status: "loading", items: [] });
const items = await search(e.target.value);
if (id === last.current) setState({ status: "done", items });
}
return (
<div>
<label>
Taom
<input onChange={change} />
</label>
{state.status === "loading" && (
<p role="status">Qidirilmoqda…</p>
)}
{state.status === "done" && state.items.length === 0 && (
<p>Hech narsa topilmadi</p>
)}
{state.items.length > 0 && (
<ul aria-label="Natijalar">
{state.items.map((d) => <li key={d}>{d}</li>)}
</ul>
)}
</div>
);
}
test("topildi — ikki taom", async () => {
const user = userEvent.setup();
render(<DishSearch search={fakeSearch} />);
await user.type(screen.getByLabelText("Taom"), "osh");
await waitForElementToBeRemoved(screen.getByRole("status"));
const list = screen.getByRole("list", { name: "Natijalar" });
console.log(list.textContent);
expect(screen.getAllByRole("listitem")).toHaveLength(2);
});
test("topilmadi", async () => {
const user = userEvent.setup();
render(<DishSearch search={fakeSearch} />);
await user.type(screen.getByLabelText("Taom"), "pizza");
expect(
await screen.findByText("Hech narsa topilmadi"),
).toBeInTheDocument();
});
test("oxirgi so'rov — to'liq so'z", async () => {
const user = userEvent.setup();
const search = vi.fn(fakeSearch);
render(<DishSearch search={search} />);
await user.type(screen.getByLabelText("Taom"), "manti");
await screen.findByRole("list", { name: "Natijalar" });
expect(search).toHaveBeenCalledTimes(5);
expect(search).toHaveBeenLastCalledWith("manti");
});Konsolda:
OshQovurma osh
✓ topildi — ikki taom
✓ topilmadi
✓ oxirgi so'rov — to'liq so'z
Testlar: 3 o'tdi (jami 3)user.type har harf uchun onChange chaqirdi — search 5 marta (m, ma, man, mant, manti). Hammasi 200 ms kutadi; id === last.current tekshiruvi bo'lmasa, oldingi so'rovlar javobi ham holatga yozilardi. Real loyihada bunday qidiruvga debounce qo'shiladi (Vitest II) — u holda test soxta soat yoki findBy ning uzunroq timeout i bilan yoziladi.
4-mashq: Vazifalar qadami
test/user-event branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling: @testing-library/user-event@14.6.7 va harakatlar.test.tsx (5 test).
Yechim
Tekshiruv ro'yxati:
npm test— 337 test;harakatlar.test.tsx— 5.- Vazifa qo'shish testida
user.typenifireEvent.change+fireEvent.submitga almashtiring — test o'tadimi? Fokus tekshiruvi (toHaveFocus) nima deydi? Qaytaring. vi.mockqatorini vaqtincha o'chiring —TilTanlovtesti jsdom'ningNot implemented: navigationxatosiga uchraydi.- Sozlash fayliga
globalThis.IS_REACT_ACT_ENVIRONMENT = true;qo'shibnpm testni yurgizing —actogohlantirishlari chiqadimi? Natijani kanon bilan solishtiring (kanonda: 349 test, 0 ogohlantirish). Keyin bitta testdanawaitni olib tashlab, ogohlantirish paydo bo'lishini ko'ring va qaytaring. npm run test:qamrovni bir necha marta yurgizing: hammasi o'tsa — yaxshi; yiqilsa, qaysi test vaDurationqancha ekanini yozing (beqarorlik Test strategiyasi da).
11. Real ishda
- Kod ko'rib chiqishda
fireEventko'p bo'lsa — "nega user-event emas?" deb so'raladi.eslint-plugin-testing-libraryningprefer-user-eventqoidasi buni avtomat tekshiradi.fireEventfaqat user-event qila olmaydigan hodisa uchun qoladi (scroll,resize, maxsusCustomEvent). - Klaviatura testi — a11y testi.
tab+keyboardbilan yozilgan bitta test Bobur yo'lini butunlay tekshiradi; to'liq a11y testlari — Accessibility testlari da. - Beqaror testlarning yarmi — noto'g'ri kutish:
findByo'rnigagetBy,waitForo'rnigasetTimeout, bo'sh hududni topish. Ular lokal kompyuterda o'tib, CI'da yiqiladi. - Intervyu savollari: "
fireEventvauser-eventfarqi?", "findByvawaitForqachon?", "actogohlantirishi nimani bildiradi?", "soxta soat bilan user-event nega qotadi?".
Xulosa
user-eventfoydalanuvchi kabi harakat qiladi: to'liq hodisalar tartibi, fokus,disabledvapointer-eventstekshiruvi.fireEvent— faqat bitta hodisa.- Har test:
const user = userEvent.setup(), har amalawait.type/keyboardda{Enter},{Backspace},{Shift>}…{/Shift}. - Forma testi — foydalanuvchining ikki yo'li: xato (matn,
aria-invalid, ulangan tavsif, fokus) va yuborish (onSubmitma'lumoti). findBy— element paydo bo'lishini,waitFor— tekshiruv o'tishini,waitForElementToBeRemoved— yo'qolishini kutadi; doim bor bo'sh hududdafindByyolg'on.actogohlantirishi — kutilmagan async yangilanish; davosi RTL kutuvchisi.globalssiz ogohlantirish jim —IS_REACT_ACT_ENVIRONMENT = true.- Soxta soat + user-event:
shouldAdvanceTime: truevaadvanceTimers; kerak bo'lmasa — haqiqiy vaqt.vazifalar: 337 test, coverage ostida birinchi beqarorlik.
Keyingi dars: RTL III: providerlar, router va hooklar bilan test — har testda takrorlanayotgan QueryClient, router va tema o'ramini bitta chiz() yordamchisiga yig'amiz, renderHook bilan hooklarni sinaymiz.
Manbalar
- user-event 14: Introduction,
setup, Options (delay,advanceTimers), Keyboard, Pointer (testing-library.com/docs/user-event/intro) - Testing Library: Async Methods —
findBy,waitFor,waitForElementToBeRemoved(testing-library.com/docs/dom-testing-library/api-async) - React:
actvaIS_REACT_ACT_ENVIRONMENT(react.dev/reference/react/act) - Kent C. Dodds, "Common mistakes with React Testing Library" —
waitForqoidalari (kentcdodds.com) - Haqiqiy chiqishlar: Vitest 5.0.4, RTL 16.3.3, user-event 14.6.7, jsdom 30.1.2, Node 24.21.0 (2026-10-20); natija oynasi — Chrome 155
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!