Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. play funksiyasi
- 2.1 Kanondagi birinchi play
- 2.2 Natija oynasida: story + play, o'zimiz yurgizamiz
- 2.3 waitFor: hodisa keyingi task'da
- 2.4 step: uzun play ni bo'lish
- 3. @storybook/addon-vitest: story = test
- 3.1 O'rnatish
- 3.2 vitest.config.ts — beshinchi loyiha
- 3.3 Qo'lda yozilmagan fayl
- 3.4 Natija
- 3.5 Birinchi o'tish: 12 dan 5–6 tasi qizil
- 4. @storybook/addon-a11y: axe har story'da
- 4.1 Sozlash
- 4.2 Biz topdik: <li> <ul> siz
- 4.3 Tajriba: nomsiz tugma
- 5. Qamrov, CI va narx
- 5.1 Story testlari qamrovga kirmadi
- 5.2 CI
- 5.3 Narx
- 6. Chromatic va jonli hujjat
- 6.1 Chromatic
- 6.2 Dizayn tizimi hujjatlari
- 7. Vazifalar qadami: story'lar — testlar (holat 12)
- 7.1 Nima o'zgardi
- 7.2 Natija va commit
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): savatcha story'si uchun play
- 3-mashq (qiyin): kichik a11y tekshiruvchi
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
Storybook II: play funksiyasi, a11y addon va Vitest bilan story testlari
Qisqacha: Story'ning play funksiyasi u chizilgandan keyin ishlaydi. Unda element topiladi (
canvas), bosiladi (userEvent) va tekshiriladi (expect) — RTL testining o'zi, faqat story ichida. Vitest addon'i (@storybook/addon-vitest) har story'ni Browser Mode testiga aylantiradi: story xatosiz chizildimi,playo'tdimi. A11y addon'i (@storybook/addon-a11y) har story'ni axe bilan tekshiradi vaa11y.test: "error"bo'lsa buzilishda testni yiqitadi. Kanonvazifalarda 12 story = 12 test, ~15 s; a11y birinchi o'tishda haqiqiy xato topdi.
Bu darsda
playfunksiyasini yozasiz:canvas,userEvent,args,step,waitFor.storybook/testdagifn,expect,waitForqayerdan kelishini tushuntirasiz.@storybook/addon-vitestbilan story'larninpm run test:storybookorqali yurgizasiz.@storybook/addon-a11yvaa11y.testning uch qiymatini qo'llaysiz.- Chromatic va "jonli hujjat" g'oyasini bilasiz — qachon kerak va narxi nima.
Oldin bilishingiz kerak: Storybook I, Vitest Browser Mode, RTL II: foydalanuvchi harakatlari, React'da accessibility.
1. Nega bu kerak?
Oldingi darsda vazifalar ga 9 ta story qo'shdik. Ular chiroyli ko'rinadi — lekin faqat kimdir Storybook'ni ochib qaraganda. Sardor Tugma ga yangi prop qo'shib, OddiyQator ni sindirib qo'ysa, buni kim ko'radi? Hech kim, toki kimdir o'sha story'ni ochmaguncha.
E'tibor bering: har story — tayyor test sharti. OddiyQator/Faol — "faol vazifa qatori xatosiz chiziladi". Tugma/Xavfli — "xavfli tugma o'chirish matni bilan chiqadi". Biz ularni allaqachon yozdik. Qolgani — ikki narsa: (1) story'larni avtomatik yurgizish, (2) "bossam nima bo'ladi?" degan savolni story ichida yozish.
Hayotiy o'xshatish: oldingi darsdagi tatib ko'rish stolida oshpaz har likopchaga qaraydi. Endi u har likopchaga kartochka qo'yadi: "tuzi — 1 choy qoshiq, 70 °C dan issiq". Har ertalab yordamchi kartochkalar bo'yicha tekshirib chiqadi. Story — likopcha, play — kartochka, Vitest — har ertalabki yordamchi.
flowchart LR
S["story fayli"] --> V["storybookTest (Vitest plagini)"]
V --> B["Browser Mode: Chrome"]
B --> R["1. chizish"]
R --> P["2. play: bosish + expect"]
P --> A["3. axe: a11y"]
A --> T["✓ yoki ×"]Rasmda: story fayli Vitest plagini orqali testga aylanadi va haqiqiy brauzerda uch bosqichdan o'tadi. Uchalasidan biri yiqilsa — test qizil.
2. play funksiyasi
2.1 Kanondagi birinchi play
vazifalar dagi Tugma story fayliga bitta story qo'shildi:
export const Bosiladi: Story = {
play: async ({ args, canvas, userEvent }) => {
await userEvent.click(
canvas.getByRole("button", { name: "Saqlash" }),
);
await expect(args.onClick).toHaveBeenCalledOnce();
},
};play — story chizilgandan keyin Storybook chaqiradigan async funksiya. U bitta obyekt oladi (story konteksti):
| Maydon | Nima |
|---|---|
canvas |
story chizilgan joy uchun so'rovlar — within(…) ning o'zi: getByRole, findByText … |
userEvent |
RTL II dagi user-event, Storybook'ga ulangan |
args |
story'ning birlashgan args'i — fn() josuslari ham shu yerda |
step |
harakatlarni nomli guruhlarga bo'lish (Interactions panelida ko'rinadi) |
mount |
chizishdan oldin biror ish qilish kerak bo'lsa (sanani soxtalash) |
Tanish narsalar: getByRole — RTL I, toHaveBeenCalledOnce — Vitest II. Yangi narsa faqat joy: test alohida *.test.tsx da emas, story ichida.
expect, fn, waitFor, userEvent — storybook/test paketidan (Storybook 9 dan beri yadroning ichida). Ichida Vitest'ning expect va josuslari, Testing Library'ning user-event va waitFor — Storybook ularni o'rab, har qadamni Interactions paneliga yozadi. Shuning uchun expect oldida await: panel har tekshiruvni qadam sifatida ko'rsatadi, xatoda qaysi qadamda to'xtaganini aytadi.
2.2 Natija oynasida: story + play, o'zimiz yurgizamiz
Storybook natija oynasida yo'q. Lekin oldingi darsdagi kabi, CSF oddiy obyekt — play esa oddiy async funksiya. Storybook nima qilishini kichik o'quv modelida takrorlaymiz: chizamiz, canvas ni within(container) qilib beramiz, play ni chaqiramiz.
import { render, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { expect, test, vi } from "vitest";
function PortionPicker({ start = 1, onChange }) {
const [count, setCount] = useState(start);
const change = (next) => {
setCount(next);
onChange(next);
};
return (
<div role="group" aria-label="Porsiya">
<button onClick={() => change(count - 1)} disabled={count <= 1}>
Kamaytirish
</button>
<output aria-label="Soni">{count}</output>
<button onClick={() => change(count + 1)}>Ko'paytirish</button>
</div>
);
}
// PortionPicker.stories.jsx
const meta = {
component: PortionPicker,
args: { onChange: vi.fn() },
};
export const Increments = {
play: async ({ args, canvas, userEvent }) => {
const plus = canvas.getByRole("button", { name: "Ko'paytirish" });
await userEvent.click(plus);
await userEvent.click(plus);
const output = canvas.getByLabelText("Soni");
await expect(output).toHaveTextContent("3");
await expect(args.onChange).toHaveBeenLastCalledWith(3);
},
};
export const MinimumIsOne = {
args: { start: 1 },
play: async ({ canvas }) => {
const minus = canvas.getByRole("button", { name: "Kamaytirish" });
await expect(minus).toBeDisabled();
},
};
// Storybook'ning ishi — o'quv modeli: chiz, keyin play
async function runStory(meta, story) {
const args = { ...meta.args, ...story.args };
const Component = meta.component;
const { container } = render(<Component {...args} />);
const canvas = within(container);
await story.play?.({ args, canvas, userEvent: userEvent.setup() });
}
test("Increments", () => runStory(meta, Increments));
test("MinimumIsOne", () => runStory(meta, MinimumIsOne));Konsolda:
✓ Increments
✓ MinimumIsOne
Testlar: 2 o'tdi (jami 2)Uch narsaga qarang:
playichidacanvas.getByRole—screenemas. Storybook bir sahifada bir nechta story'ni (Docs sahifasida) chizadi;canvasqidiruvni faqat shu story bilan cheklaydi.story.play?.(…)—playsiz story ham story: u faqat "xatosiz chizildi" deb tekshiriladi. Bunday testni tutun testi (smoke test) deyishadi — "yoqib ko'rdik, tutun chiqmadi".MinimumIsOneda harakat yo'q, faqatexpect—playholatni tekshirish uchun ham yoziladi.
runStory — modelning yuragi, haqiqiy Storybook'da undan ancha ko'p ish bor: dekoratorlar, loaders, parameters, har qadamni panelga yozish, josuslarni tozalash. Lekin tartib shu: args → chizish → play.
2.3 waitFor: hodisa keyingi task'da
Kanondagi uchinchi play — OchirishDialog/Tasdiq:
export const Tasdiq: Story = {
play: async ({ args, canvas, userEvent }) => {
await userEvent.click(
canvas.getByRole("button", { name: "O'chirish" }),
);
// close hodisasi — keyingi task'da (form method="dialog")
await waitFor(() =>
expect(args.onJavob).toHaveBeenCalledWith(true),
);
},
};Birinchi variantda waitFor yo'q edi — test yiqildi. Sabab: <form method="dialog"> dagi tugma bosilganda brauzer dialogni yopadi, close hodisasi esa keyingi taskda keladi (event loop). click tugadi, onJavob hali chaqirilmagan. Xuddi shu holatni natija oynasida kechikkan javob bilan ko'ramiz:
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { expect, test, vi } from "vitest";
// Javob keyinroq keladi — dialog close hodisasidagi kabi
function ConfirmDelete({ onAnswer }) {
const answer = (value) => setTimeout(() => onAnswer(value), 50);
return (
<div role="dialog" aria-label="Osh o'chirilsinmi?">
<button onClick={() => answer(false)}>Bekor qilish</button>
<button onClick={() => answer(true)}>O'chirish</button>
</div>
);
}
test("waitFor'siz", async () => {
const onAnswer = vi.fn();
const user = userEvent.setup();
render(<ConfirmDelete onAnswer={onAnswer} />);
await user.click(screen.getByRole("button", { name: "O'chirish" }));
expect(onAnswer).toHaveBeenCalledWith(true);
});
test("waitFor bilan", async () => {
const onAnswer = vi.fn();
const user = userEvent.setup();
render(<ConfirmDelete onAnswer={onAnswer} />);
await user.click(screen.getByRole("button", { name: "O'chirish" }));
await waitFor(() => expect(onAnswer).toHaveBeenCalledWith(true));
});Konsolda:
× waitFor'siz
→ expected "vi.fn()" to be called with arguments: [ true ]
Number of calls: 0
✓ waitFor bilan
Testlar: 1 yiqildi, 1 o'tdi (jami 2)Nega 50 ms, 0 emas? Biz avval setTimeout(…, 0) ni sinadik — ikkala test ham o'tdi. Sabab: user-event har harakatdan keyin delay qadar kutadi (sukut 0 — bitta setTimeout(0)), va shu kutish ichida 0 ms li javob ham kelib ulguradi. Kanondagi dialogda bu yetmadi. Qoida o'zgarmaydi: "keyinroq" keladigan narsaga — waitFor, vaqtni taxmin qilishga emas.
waitFor ichidagi funksiyani u o'tguncha (sukut bo'yicha 1 s gacha, har 50 ms) qayta chaqiradi (RTL II). Number of calls: 0 — josus umuman chaqirilmagan: "argument boshqa" emas, "chaqiruv yo'q". Haqiqiy Vitest 5.0.4 da ham (biz alohida ishga tushirdik) xabar shunday, faqat oldida bo'sh qator va kod bo'lagi bor — natija oynasi konsoli bo'sh qatorlarni tashlab yuboradi.
2.4 step: uzun play ni bo'lish
Ko'p qadamli story'da (forma: ism, telefon, yuborish) step harakatlarni nomlaydi:
export const Yuborildi: Story = {
play: async ({ canvas, userEvent, step }) => {
await step("Formani to'ldirish", async () => {
await userEvent.type(canvas.getByLabelText("Ism"), "Dilshod");
await userEvent.type(
canvas.getByLabelText("Telefon"),
"+998 90 000 00 00",
);
});
await step("Yuborish", async () => {
const send = canvas.getByRole("button", { name: "Bron" });
await userEvent.click(send);
});
},
};Interactions panelida ikkita yig'iladigan guruh chiqadi. Testga ta'siri yo'q — faqat o'qish va xatoni topish oson. vazifalar da play lar qisqa, step ishlatilmagan.
Tekshirib ko'ring:
playichidascreen.getByRole("button")yozilsa, Storybook'ning Docs sahifasida nima bo'lishi mumkin?
Javob
Docs sahifasida bir faylning hamma story'lari bitta sahifada chiziladi — screen hammasini ko'radi va Found multiple elements… xatosi chiqadi (yoki boshqa story'ning tugmasi bosiladi). canvas faqat shu story ichidan qidiradi.
3. @storybook/addon-vitest: story = test
3.1 O'rnatish
git switch -c test/storybook-vitest
npm i -D -E @storybook/addon-vitest@10.6.1 \
@storybook/addon-a11y@10.6.1+3 paket (ikki addon va axe-core), 13 s. main.ts da:
// 22/#12: a11y paneli (axe) va story'larni
// Vitest testiga aylantirish
addons: ["@storybook/addon-a11y", "@storybook/addon-vitest"],3.2 vitest.config.ts — beshinchi loyiha
Browser Mode darsida vitest.config.ts da loyihalar (mantiq, komponent, brauzer, tarmoq) bor edi. Yana bittasi qo'shildi:
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin"; {
// 22/#12: har story — test: render (xatosiz
// chiziladi), play funksiyasi (harakat + expect)
// va a11y (axe, preview.tsx). Story fayllarining
// o'zi — testlar; alohida *.test.tsx yozilmaydi
extends: true,
plugins: [storybookTest({ configDir: ".storybook" })],
test: {
name: "storybook",
browser: {
enabled: true,
headless: true,
provider: playwright({
launchOptions: { channel: "chrome" },
}),
instances: [{ browser: "chromium" }],
},
},
},storybookTest({ configDir })— Vitest plagini:.storybook/main.tsdagistoriesglobini o'qiydi va har story'ni test qiladi.includeyozilmaydi — fayllar ro'yxati Storybook'dan.browser— #08 dagi o'sha sozlama: tizimdagi Chrome, headless, brauzer yuklanmaydi.- Skript:
"test:storybook": "vitest run --project storybook".npm testga kirmadi — Chrome talab qiladi va sekinroq.
Bu darsdagi kanon izohlari telefonga sig'ishi uchun qatorlarga bo'lingan — kod va ma'no o'zgarmagan.
3.3 Qo'lda yozilmagan fayl
Eski qo'llanmalarda .storybook/vitest.setup.ts bor: unda setProjectAnnotations([...]) chaqiriladi. vazifalar da bu fayl yo'q. Storybook 10 da addon annotatsiyalarni (preview'dagi dekoratorlar, a11y sozlamasi) o'zi qo'llaydi, MIGRATION.md esa ogohlantiradi: qo'lda chaqirish addon'ning o'z annotatsiyalarini almashtirib yuborishi mumkin. Eski maqolada ko'rsangiz — Storybook versiyasini tekshiring.
3.4 Natija
npm run test:storybook Test Files 4 passed (4)
Tests 12 passed (12)
Duration 9.99s (setup 54%, tests 33%, worker 8%, import 4%)9 ta eski story + 3 ta play li = 12 test. Devor vaqti 14.8–15.1 s (uch o'lchov), Duration 10.0–10.9 s. Vaqtning yarmidan ko'pi setup — brauzer va Storybook preview'ni tayyorlash. Taqqoslash uchun — bitta test o'rtacha:
- Vitest jsdom (hamma test)348 test, 12.68 s36 ms
- Browser Mode6 test, 8.33 s1 390 ms
- Storybook (Browser Mode)12 test, 9.99 s830 ms
Manba: O'lchandi: vazifalar kanoni (holat 12, tekshiruv-12), i5-12500H, Windows 11, Node 24.21.0, Vitest 5.0.4, Chrome 155, 2026-10-20
Grafik Duration ni testlar soniga bo'ladi — birinchi darsdagi grafik bilan bir xil usul. Ishga tushish narxi ham ichida, shuning uchun test qancha ko'p bo'lsa, bittasiga shuncha kam tushadi. O'sha 6 ta brauzer testi holat 08 da 3.84 s (≈0.64 s/test, birinchi darsdagi ≈0.7 s) edi, holat 12 da 8.33 s — bir xil test, kompyuter yuki boshqa. Shuning uchun aniq soniyaga emas, tartibga qarang. Xulosa o'zgarmaydi: brauzerdagi test jsdom'dagidan o'nlab baravar qimmat. Story testlari jsdom testlarini almashtirmaydi — ular brauzerga muhtoj komponentlar va "har holat xatosiz chiziladi" kafolati uchun.
3.5 Birinchi o'tish: 12 dan 5–6 tasi qizil
Birinchi ishga tushirishda testlarning deyarli yarmi yiqildi (alohida nusxada o'lchandi):
| Nechta | Nima | Tuzatish |
|---|---|---|
| 4 | router xatosi (basename) |
preview.tsx dekoratori (o'shanda hali yo'q edi) |
| 1 | Tasdiq — onJavob chaqirilmagan |
waitFor («waitFor: hodisa keyingi task'da» bo'limi) |
| 1 | UchVazifa — a11y |
keyingi bo'lim |
Diqqat: oldingi darsda story'lar Storybook'da ko'rinib turgandek edi — router xatosi faqat ochilgan story'da qizil qutida chiqadi. Kimdir hamma story'ni ochib chiqmaguncha, xato yashiringan. Test hammasini har safar ochadi.
4. @storybook/addon-a11y: axe har story'da
4.1 Sozlash
preview.tsx ga:
parameters: {
// 22/#12: addon-a11y — har story axe bilan
// tekshiriladi; "error" — buzilish bo'lsa Vitest'da
// test YIQILADI ("todo" — faqat ogohlantirish)
a11y: { test: "error" },
},axe (axe-core) — Deque kompaniyasining ochiq kodli a11y tekshiruvchisi (HTML a11y va 18-qism da ko'rgan edik). Addon har story chizilgach (va play dan keyin) axe'ni o'sha story ustida ishga tushiradi. Storybook'dagi Accessibility panelida natija: buzilishlar, o'tganlar, tekshirib bo'lmaganlar.
a11y.test uch qiymat oladi:
| Qiymat | Vitest'da |
|---|---|
"error" |
buzilish — test yiqiladi |
"todo" |
buzilish — ogohlantirish, test o'tadi |
"off" |
axe testda ishlamaydi |
Butun loyiha uchun "error", hali tuzatilmagan bitta story uchun parameters: { a11y: { test: "todo" } } — eski loyihaga a11y'ni bosqichma-bosqich kiritishning yo'li.
4.2 Biz topdik: <li> <ul> siz
Birinchi o'tishda VazifaRoyxati/UchVazifa yiqildi:
<li> elements must be contained in a <ul> or <ol> (listitem)Tarjima: "<li> elementlari <ul> yoki <ol> ichida bo'lishi kerak (listitem qoidasi)". VazifaRoyxati faqat <li> larni qaytaradi, <ul> esa App da turadi — ilovada hammasi to'g'ri. Story komponentni yolg'iz chizgani uchun <li> lar ota-onasiz qoldi. Ekran o'quvchi bunday ro'yxatni "ro'yxat, 3 band" deb e'lon qila olmaydi.
Tuzatish — story o'ramida, ilovada emas:
{/* Komponent faqat <li> lar beradi — <ul> App da
(a11y: listitem) */}
<ul>
<Story />
</ul>Xato ilovada emas, story'da edi — lekin baribir foydali topilma: u komponentning shartnomasini ko'rsatdi ("meni <ul> ichiga qo'ying"). Izoh shu shartnomani yozib qo'ydi.
4.3 Tajriba: nomsiz tugma
Addon haqiqatan yiqitadimi — ataylab tekshirdik: vaqtinchalik ZzSinov.stories.tsx da matnsiz <button>. Haqiqiy Vitest chiqishi (qisqartirilmagan):
RUN v5.0.4 C:/Users/…/vazifalar
× Nomsiz Tugma 1471ms
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯
FAIL |storybook (chromium)| stories/ZzSinov.stories.tsx > Nomsiz Tugma
Error:
Click to debug the error directly in Storybook: http://localhost:6006/?path=/story/zzsinov--nomsiz-tugma&addonPanel=storybook/interactions/panel
expect(received).toHaveNoViolations(expected)
Expected the HTML found at $('.tugma') to have no violations:
<button type="button" class="tugma"></button>
Received:
"Buttons must have discernible text (button-name)"
Fix any of the following:
Element does not have inner text that is visible to screen readers
aria-label attribute does not exist or is empty
aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty
Element has no title attribute
Element does not have an implicit (wrapped) <label>
Element does not have an explicit <label>
Element's default semantics were not overridden with role="none" or role="presentation"
You can find more information on this issue here:
https://dequeuniversity.com/rules/axe/4.14/button-name?application=axeAPI
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯
Test Files 1 failed (1)
Tests 1 failed (1)
Start at 11:12:41O'qiymiz:
|storybook (chromium)|— loyiha nomi va brauzer; keyin fayl va story nomi (Nomsiz Tugma— eksport nomiNomsizTugmadan).Buttons must have discernible text (button-name)— "tugmaning ajratib bo'ladigan matni bo'lishi kerak". Qavsda qoida nomi.Fix any of the following— "quyidagilardan birini tuzating": ichki matn,aria-label,aria-labelledby,title… Bittasi yetarli.- Oxiridagi havola — qoidaning to'liq tavsifi (axe 4.14 — addon o'zi olib kelgan versiya).
vazifalar ning o'z Tugma sida bu xato bo'lishi mumkin emas: 18-qismda turlar matnsiz va aria-label siz tugmani tsc darajasida taqiqlagan. Tajriba fayli o'chirildi.
axe'ning natija oynasida ishlaydigan nusxasi yo'q. Uning bitta qoidasini — "har tugmaning nomi bor" — RTL bilan o'zimiz yozsak bo'ladi:
import { render, screen } from "@testing-library/react";
import { expect, test } from "vitest";
function Toolbar() {
return (
<div role="toolbar" aria-label="Buyurtma">
<button>Saqlash</button>
<button>
<svg aria-hidden="true" width="16" height="16" />
</button>
</div>
);
}
test("har tugmaning nomi bor", () => {
render(<Toolbar />);
for (const btn of screen.getAllByRole("button")) {
expect(btn).toHaveAccessibleName(/.+/);
}
});Konsolda:
× har tugmaning nomi bor
→ expected <button> to have accessible name /.+/, received ''
Testlar: 1 yiqildi, 0 o'tdi (jami 1)/.+/ — "kamida bitta belgi" (RegExp). Birinchi tugma o'tdi, ikkinchisining nomi bo'sh. Bu — axe'ning yuzlab qoidasidan bittasi; to'liq a11y testlari Accessibility testlari da.
Tekshirib ko'ring: Jamoangiz 200 story'li eski loyihaga a11y addon qo'shdi, 40 story qizil. Ertaga reliz. Nima qilasiz?
Javob
preview da a11y: { test: "todo" } — hamma buzilish ogohlantirish bo'lib ko'rinadi, reliz to'xtamaydi. Keyin story'larni bittalab tuzatib, har tuzatilgan faylda "error" ga o'tkazasiz (yoki aksincha: umumiy "error", 40 story'da vaqtincha "todo"). Muhimi — ro'yxat ko'rinib tursin; "off" bilan muammo yo'qolmaydi, faqat ko'rinmay qoladi.
5. Qamrov, CI va narx
5.1 Story testlari qamrovga kirmadi
Tugma va OddiyQator Vitest II dagi qamrov hisobotida 0 % edi — endi story'lar ularni chizadi. Lekin npm run test:qamrov faqat mantiq va komponent loyihalarini o'lchaydi. Browser Mode'da qamrov (@vitest/coverage-v8 brauzerda) — alohida sozlash va alohida o'lchov kerak; kanonga kirmadi. Halol raqam: qamrov 62.72 % (satrlar) — #11 dagidek.
5.2 CI
.github/workflows/test.yml ning brauzer job'iga bitta qadam:
# 22/#12: story'lar — render, play, a11y (axe)
# — o'sha Chrome'da
- run: npm run test:storybookactionlint — 0 xato. Playwright II dagidek: workflow yozilgan va tekshirilgan, lekin push qilinmagan va GitHub Actions'da ishlamagan. O'z repongizda birinchi push'dan keyin natijani o'zingiz ko'rasiz.
5.3 Narx
| O'lchov | Qiymat |
|---|---|
| O'rnatish | +3 paket, 13 s |
node_modules |
Storybook jami +58 MB (375 → 433 MB) |
test:storybook |
12 test, devor ~15 s |
| Ilova JS | 206.7 KB — o'zgarmadi |
@storybook/addon-a11y o'zi bilan axe-core 4.14.0 ni olib keldi. Accessibility testlari darsida Playwright uchun boshqa axe paketi qo'shiladi — o'shanda bitta loyihada ikki nusxa bo'ladi. Buni o'sha darsda o'lchaymiz.
6. Chromatic va jonli hujjat
6.1 Chromatic
Chromatic — Storybook mualliflari jamoasining bulut xizmati. Hujjatiga ko'ra: Storybook build'ini yuklaydi (jamoa havola orqali ko'radi), har story'ning suratini oladi va oldingisi bilan solishtiradi (visual test), o'zgarishni dizayner yoki ko'rib chiquvchi "qabul qilaman" deb belgilaydi. Paketlar: chromatic CLI (18.13.0) va @chromatic-com/storybook addon'i (5.6.0) — 2026-10 holatiga.
vazifalar da Chromatic ishlatilmagan: hisob kerak, va kursda hisob ochmaymiz. Suratlarni solishtirish g'oyasini keyingi darsda Playwright bilan, o'z mashinamizda qilamiz — Chromatic xuddi shu ishni bulutda va story'lar darajasida qiladi.
6.2 Dizayn tizimi hujjatlari
Story'lar, autodocs (Storybook I) va MDX sahifalar birga jonli hujjat bo'ladi. MDX — oddiy matn ichida jonli story'lar. Hujjatda: tokenlar sahifasi (dizayn tizimi), har komponentning variantlari, "qachon ishlatilmaydi" bo'limi. storybook build natijasi (statik sayt) jamoaning ichki manziliga qo'yiladi. Kod o'zgarsa, hujjat ham o'zgaradi — eskirgan PDF qo'llanmadan farqi shu.
Story'lardan RTL testlarida ham foydalanish mumkin: @storybook/react dagi composeStories story'ni oddiy komponentga aylantiradi (dekorator va args bilan) — jsdom'dagi *.test.tsx da render(<Primary />). Bu portativ story'lar (portable stories) deyiladi; vazifalar da ishlatilmadi — story'lar allaqachon Vitest'da test.
7. Vazifalar qadami: story'lar — testlar (holat 12)
7.1 Nima o'zgardi
| Fayl | O'zgarish |
|---|---|
package.json |
2 addon, test:storybook skripti |
.storybook/main.ts |
addons |
.storybook/preview.tsx |
parameters.a11y.test = "error" |
vitest.config.ts |
storybook loyihasi |
stories/* |
3 ta play: Tugma/Bosiladi, OddiyQator/Belgilash, OchirishDialog/Tasdiq |
test.yml |
brauzer job'ida test:storybook |
OddiyQator/Belgilash — katakchani bosadi va fayl darajasidagi amallar.onAlmashtir josusini tekshiradi:
export const Belgilash: Story = {
play: async ({ canvas, userEvent }) => {
await userEvent.click(
canvas.getByRole("checkbox", { name: "Non olish #bozor" }),
);
await expect(amallar.onAlmashtir).toHaveBeenCalledWith(1);
},
};args emas, amallar — josus kontekst orqali berilgan (Storybook I dagi dekorator), args'da emas.
7.2 Natija va commit
Testlar: Vitest 348, brauzer 6, storybook 12, e2e 10. Lint, format:check, tip, knip — toza. JS 206.7 KB.
git add -A
git commitCommit (kanon): test: story'lar Vitest testi — play funksiyalari va addon-a11y (axe).
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
expected "vi.fn()" to be called… darhol |
hodisa keyingi task'da | await waitFor(() => expect(…)) |
Found multiple elements… Docs'da |
play da screen |
canvas |
| Interactions panelida qadamlar yo'q | expect oldida await yo'q |
await expect(…) |
listitem, button-name … |
axe qoidasi | o'ramni yoki komponentni tuzating; vaqtincha "todo" |
| dekoratorlar testda ishlamaydi | qo'lda setProjectAnnotations |
Storybook 10 da faylni olib tashlang |
Executable doesn't exist |
Playwright brauzeri yo'q | channel: "chrome" yoki npx playwright install chromium |
9. Mashqlar
Mashqlar kurs/mashqlar/22/12-storybook-play/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- Story chizilgandan keyin ishlaydigan funksiya —
- Faqat shu story ichidan qidiradigan so'rovlar —
- Buzilish bo'lsa ham test o'tsin, faqat ogohlantirsin —
a11y: { test: "" } - Story'larni testga aylantiradigan Vitest plagini —
- Hodisa keyinroq keladi —
await (() => expect(…))
Yechim
- play —
async ({ args, canvas, userEvent }) => { … }. - canvas —
within(story elementi);screenbutun sahifani ko'radi. - todo —
"error"yiqitadi,"off"o'chiradi. - storybookTest —
@storybook/addon-vitest/vitest-plugindan. - waitFor — o'tguncha qayta urinadi (sukut 1 s).
2-mashq (o'rta): savatcha story'si uchun play
CartButton — «Savatga qo'shish» tugmasi; bosilganda onAdd(dishId) chaqiradi va matn «Savatda » ga o'zgaradi, tugma o'chiriladi. Meta: args: { dishId: 12, onAdd: vi.fn() }. AddsOnce story'sini yozing: play tugmani ikki marta bosadi (userEvent.dblClick emas — ikki click), keyin onAdd bir marta va 12 bilan chaqirilganini, tugma disabled ekanini tekshiradi. «Story + play» bo'limidagi runStory bilan yurgizing.
Ishora: o'chirilgan tugmani user-event bosmaydi — ikkinchi click hech narsa qilmaydi; toHaveBeenCalledExactlyOnceWith (Vitest II).
Yechim
import { render, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
import { expect, test, vi } from "vitest";
function CartButton({ dishId, onAdd }) {
const [added, setAdded] = useState(false);
const add = () => {
setAdded(true);
onAdd(dishId);
};
return (
<button onClick={add} disabled={added}>
{added ? "Savatda ✓" : "Savatga qo'shish"}
</button>
);
}
const meta = {
component: CartButton,
args: { dishId: 12, onAdd: vi.fn() },
};
const AddsOnce = {
play: async ({ args, canvas, userEvent }) => {
const btn = canvas.getByRole("button", {
name: "Savatga qo'shish",
});
await userEvent.click(btn);
await userEvent.click(btn);
await expect(args.onAdd).toHaveBeenCalledExactlyOnceWith(12);
await expect(btn).toBeDisabled();
console.log(btn.textContent);
},
};
async function runStory(meta, story) {
const args = { ...meta.args, ...story.args };
const Component = meta.component;
const { container } = render(<Component {...args} />);
await story.play?.({
args,
canvas: within(container),
userEvent: userEvent.setup(),
});
}
test("AddsOnce", () => runStory(meta, AddsOnce));Konsolda:
Savatda ✓
✓ AddsOnce
Testlar: 1 o'tdi (jami 1)btn o'zgaruvchisi bosishdan keyin ham o'sha element — React matnni yangiladi, elementni almashtirmadi. Shuning uchun nom o'zgargan bo'lsa ham toBeDisabled uni tekshira oladi.
3-mashq (qiyin): kichik a11y tekshiruvchi
Story'lar uchun axe'ning ikki qoidasini o'zingiz yozing — checkA11y(container) funksiyasi buzilishlar massivini qaytarsin: (1) button-name — nomsiz tugma; (2) listitem — ota-onasi <ul>/<ol> bo'lmagan <li>. Keyin ikki story bilan sinang: Broken (nomsiz tugma va yalang'och <li>) — 2 buzilish; Fixed — 0. Har buzilishni qoida: element ko'rinishida konsolga chiqaring.
Ishora: tugma nomini RTL'ning o'zi hisoblaydi — computeAccessibleName @testing-library/dom ichida, lekin eksport qilinmagan; oddiyroq yo'l — btn.textContent.trim(), aria-label va aria-labelledby ni tekshirish. <li> uchun li.parentElement.tagName.
Yechim
import { render } from "@testing-library/react";
import { expect, test } from "vitest";
function checkA11y(container) {
const problems = [];
for (const btn of container.querySelectorAll("button")) {
const named =
btn.textContent.trim() !== "" ||
btn.getAttribute("aria-label") ||
btn.getAttribute("aria-labelledby");
if (!named) problems.push(`button-name: ${btn.outerHTML}`);
}
for (const li of container.querySelectorAll("li")) {
const parent = li.parentElement.tagName;
if (parent !== "UL" && parent !== "OL") {
problems.push(`listitem: ${li.outerHTML}`);
}
}
return problems;
}
function Broken() {
return (
<div>
<li>Osh</li>
<button>
<svg aria-hidden="true" width="16" height="16" />
</button>
</div>
);
}
function Fixed() {
return (
<div>
<ul>
<li>Osh</li>
</ul>
<button aria-label="Oshni o'chirish">
<svg aria-hidden="true" width="16" height="16" />
</button>
</div>
);
}
test("Broken — 2 buzilish", () => {
const { container } = render(<Broken />);
const problems = checkA11y(container);
for (const p of problems) console.log(p);
expect(problems).toHaveLength(2);
});
test("Fixed — 0", () => {
const { container } = render(<Fixed />);
expect(checkA11y(container)).toEqual([]);
});Konsolda:
button-name: <button><svg aria-hidden="true" width="16" height="16"></svg></button>
listitem: <li>Osh</li>
✓ Broken — 2 buzilish
✓ Fixed — 0
Testlar: 2 o'tdi (jami 2)Bu yerda querySelectorAll — to'g'ri tanlov: biz foydalanuvchi oqimini emas, DOM tuzilmasini tekshiryapmiz (axe ham shunday qiladi). Haqiqiy axe ancha aqlli: title, <label>, role="presentation" ni ham hisobga oladi, aria-labelledby havolasi bo'sh emasligini tekshiradi. O'z tekshiruvchingizni loyihada ishlatmang — bu mashq axe ichini tushunish uchun.
4-mashq: Vazifalar qadami
test/storybook-vitest branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling: ikki addon, storybook loyihasi, a11y.test, uch play, CI qadami.
Yechim
Tekshiruv ro'yxati:
npm run test:storybook— 4 fayl, 12 test.TasdiqdagiwaitForni olib tashlang — test yiqiladi (onJavobhali chaqirilmagan). Qaytaring.VazifaRoyxatistory o'ramidagi<ul>ni olib tashlang —UchVazifalistitembilan yiqiladi. Qaytaring.- Vaqtincha matnsiz tugma story'si —
button-name; keyin faylni o'chiring. npx storybook dev—Tugma/Bosiladida Interactions panelida qadamlar ko'rinadi.
10. Real ishda
- Dizayn tizimi jamoalari story'larni asosiy test birligi qiladi: komponent kutubxonasida har variant — story, har story — test va a11y tekshiruvi.
- Pull request'da story testlari CI'da yuradi; Chromatic kabi xizmatlar esa suratlar farqini ko'rib chiquvchiga ko'rsatadi.
- "Yozilgan story — bepul test": jamoalar ko'pincha yangi komponentga avval story yozadi,
playni keyin qo'shadi — tutun testi birinchi kundan bor. - Intervyu savollari: "
playfunksiyasi nima?", "story testi va RTL testining farqi?", "axe nimani ushlamaydi?" (klaviatura oqimi, mazmunli matn — Accessibility testlari).
Xulosa
play— story chizilgach ishlaydigan test:canvas,userEvent,args,expect(storybook/test);playsiz story — tutun testi.- Hodisa keyingi task'da kelsa —
waitFor;canvasniscreeno'rniga ishlating. @storybook/addon-vitest(storybookTest) har story'ni Browser Mode testiga aylantiradi:vazifalarda 12 test, ~15 s.@storybook/addon-a11y+a11y.test: "error"— axe buzilishi testni yiqitadi;"todo"— bosqichma-bosqich kiritish.- Birinchi o'tish 4 router xatosi, 1
waitForva 1 a11y topilmasini ko'rsatdi — story'lar ochilmaguncha xato yashirin edi. - Chromatic — bulutdagi visual test va hujjat xizmati (hisob kerak, kanonda yo'q).
Keyingi dars: Visual regression testlar — sahifa suratini oltin rasm bilan piksel bo'yicha solishtirish va vazifalar dagi 20-qismdan beri yashirin turgan xatoni topish.
Manbalar
- Storybook: Interaction testing (
play,canvas,step,mount), Accessibility testing (parameters.a11y.test), Vitest addon (storybook.js.org/docs/writing-tests) - Storybook MIGRATION.md —
storybook/test,setProjectAnnotations, essentials yadroga ko'chgani - axe-core qoidalari: dequeuniversity.com/rules/axe/4.14 (
button-name,listitem) - Chromatic — chromatic.com/docs (hujjat bo'yicha; kanonda hisob ochilmagan)
- Haqiqiy chiqishlar: Vitest 5.0.4, Storybook 10.6.1, Chrome 155, Node 24.21.0 (2026-10-20)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!