# QA 17: QA uchun TypeScript — o'zgaruvchilar, funksiyalar, async/await, class'lar va npm

> **Holat:** 2026-yil oktabr. Bu darsda avtomatlashtirish uchun kerak bo'ladigan TypeScript bazasini QA misollarida o'rganamiz. Muhit: Node.js 24 LTS, TypeScript 7.0.2, tsx 4.23, @faker-js/faker 10.6.0. Darsdagi har bir kod namunasi shu versiyalarda ishga tushirib tekshirilgan.

> **Bu darsdan keyin siz:** Playwright testlarini o'qib tushunadigan va o'zingiz yozadigan darajada TypeScript bilasiz.

- TypeScript loyihasini noldan sozlaysiz va kodni `tsx` bilan ishga tushirasiz;
- tiplar, interface, funksiya, massiv metodlari va class'lar bilan ishlaysiz;
- `async/await` qanday ishlashini va unutilgan `await` nega beqaror testlarga olib kelishini tushunasiz;
- `fetch` bilan haqiqiy API'ga so'rov yuborib, javobni tekshirasiz;
- faker bilan test ma'lumotlari yaratasiz va kichik API smoke skript yozasiz.

## Mundarija

1. [Nega QA'ga dasturlash va nega TypeScript](#nega)
2. [Muhitni sozlash](#setup)
3. [O'zgaruvchilar va tiplar](#tiplar)
4. [Interface va type: DemoBozor modellari](#interface)
5. [Funksiyalar](#funksiyalar)
6. [Shartlar, switch va sikllar](#shartlar)
7. [Massivlar va obyektlar](#massivlar)
8. [Modullar va npm](#modullar)
9. [Class'lar — Page Object poydevori](#classlar)
10. [Asinxron kod: Promise va async/await](#async)
11. [fetch: API'ga birinchi so'rov](#fetch)
12. [Regex, sanalar va muhit o'zgaruvchilari](#regex-sana-env)
13. [faker bilan test ma'lumotlari](#faker)
14. [Mini-loyiha: API smoke skripti](#mini-loyiha)
15. [Amaliy mashqlar](#mashqlar)
16. [O'zingizni tekshiring](#checklist)
17. [Xulosa](#xulosa)
18. [Manbalar](#manbalar)

---

## 1. Nega QA'ga dasturlash va nega TypeScript {#nega}

[QA 16](/ios-lessons/qa-automation-intro) da ko'rdik: avtotest — test case'ning kod ko'rinishi. Demak, avtomatlashtirish uchun dasturlash tilini bilish shart. Lekin QA'ga dasturchining hamma bilimi kerak emas: o'zgaruvchilar, funksiyalar, massivlar, class'lar va asinxron kod — 90% ish shular bilan bajariladi.

**TypeScript (TS)** — bu JavaScript + tiplar. Brauzer va Node.js oxir-oqibat JavaScript'ni bajaradi, TypeScript esa kodni ishga tushirishdan **oldin** xatolarni topib beradi:

```typescript
function calculateDeliveryFee(subtotal: number): number {
  return subtotal >= 300_000 ? 0 : 20_000;
}

calculateDeliveryFee("250000");
// tsc: Argument of type 'string' is not assignable to parameter of type 'number'.
```

QA uchun TypeScript'ning foydasi:

- **Xatolar test ishlashidan oldin ko'rinadi** — `page.gotoo()` kabi yozuv xatosi CI'da 10 daqiqadan keyin emas, muharrirda darhol qizaradi.
- **Avtomatik to'ldirish (autocomplete)** — `page.` deb yozsangiz, VS Code barcha metodlarni ko'rsatadi.
- **Playwright hujjatlari TypeScript'da** — misollarni to'g'ridan-to'g'ri ko'chirasiz.
- **Frontend jamoa bilan bitta til** — React/Angular dasturchilari kodingizni review qila oladi.

> **🧠 Esda tuting:** Playwright TypeScript'ni o'zi JavaScript'ga aylantirib ishga tushiradi, lekin **tiplarni tekshirmaydi**. Rasmiy tavsiya: tiplarni alohida `npx tsc --noEmit` bilan tekshirish (CI'da ham).

---

## 2. Muhitni sozlash {#setup}

### 1-qadam: Node.js va VS Code

1. [nodejs.org](https://nodejs.org/en/download) dan **Node.js 24 LTS** ni o'rnating.
2. Terminalda tekshiring:

```bash
node -v   # v24.x.x
npm -v
```

3. [VS Code](https://code.visualstudio.com/) va kengaytmalar: **Playwright Test for VS Code** (Microsoft), ESLint, Prettier.

### 2-qadam: loyiha yaratish

```bash
mkdir qa-ts-basics && cd qa-ts-basics
npm init -y
npm pkg set type=module              # zamonaviy import/export (ES modules)
npm i -D typescript tsx @types/node@24
mkdir src
```

| Paket            | Nima uchun                                                           |
| ---------------- | -------------------------------------------------------------------- |
| `typescript`     | `tsc` — tiplarni tekshiruvchi kompilyator (hozir 7.0.2)              |
| `tsx`            | `.ts` faylni kompilyatsiyasiz darhol ishga tushiradi                 |
| `@types/node@24` | Node.js API tiplari (`process`, `fetch`...) — Node versiyangizga mos |

### 3-qadam: `tsconfig.json`

`npx tsc --init` uzun konfiguratsiya yaratadi. Boshlash uchun quyidagi qisqasi yetarli — uni `tsconfig.json` nomi bilan saqlang:

```json
{
  "compilerOptions": {
    "target": "ES2023",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "moduleDetection": "force",
    "types": ["node"],
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}
```

| Sozlama                       | Ma'nosi                                                                                                 |
| ----------------------------- | ------------------------------------------------------------------------------------------------------- |
| `strict: true`                | Qat'iy tekshiruv — eng ko'p xatoni ushlaydi. Doim yoqing                                                |
| `noEmit: true`                | `tsc` JS fayl yaratmaydi, faqat tekshiradi (ishga tushirishni `tsx` qiladi)                             |
| `types: ["node"]`             | Node.js tiplarini ulaydi. TypeScript 7 da standart qiymat `[]` bo'lib qoldi, shuning uchun yozish shart |
| `moduleResolution: "Bundler"` | `import { x } from './pricing'` — kengaytmasiz import ishlaydi                                          |
| `moduleDetection: "force"`    | Har bir fayl alohida modul — fayllar orasida nomlar to'qnashmaydi                                       |

### 4-qadam: birinchi fayl

`src/hello.ts`:

```typescript
const appName = "DemoBozor";
let passedTests = 0;
passedTests = passedTests + 1;

console.log(`${appName}: ${passedTests} ta test o'tdi`);
```

```bash
npx tsx src/hello.ts   # natija: DemoBozor: 1 ta test o'tdi
npx tsc                # xato bo'lmasa — hech narsa chiqarmaydi
```

> **⚠️ Diqqat:** TypeScript 7.0 2026-yil 8-iyulda chiqdi — kompilyator Go tilida qayta yozilgan (Microsoft uni "10 barobar tezroq native port" deb e'lon qilgan). Lekin 7.0 da dasturiy API yo'q, shuning uchun `typescript-eslint` kabi asboblar hozircha TypeScript 6.0 ga tayanadi (2026-yil oktabr holatida `typescript-eslint` 8.71 `typescript <6.1.0` talab qiladi). Microsoft bunday holatlar uchun `@typescript/typescript6` moslik paketini taklif qilgan. Oddiy o'rganish va Playwright testlari uchun bu sizga xalaqit bermaydi.

---

## 3. O'zgaruvchilar va tiplar {#tiplar}

```typescript
const productName: string = "Elektr choynak";
const price: number = 185_000; // so'm; _ faqat o'qish uchun
const inStock: boolean = true;

const prices: number[] = [185_000, 95_000, 1_200_000];
const categories: string[] = ["oshxona", "kiyim", "elektronika"];

type OrderStatus =
  | "CREATED"
  | "PENDING_PAYMENT"
  | "PAID"
  | "PACKING"
  | "SHIPPED"
  | "DELIVERED"
  | "CANCELLED"
  | "RETURNED";

let status: OrderStatus = "CREATED";
status = "PAID";

let orderId: number | string = 42; // union: son YOKI matn
orderId = "ORD-42";
```

- **`const`** — qayta tayinlanmaydi. Standart tanlov shu bo'lsin.
- **`let`** — qiymat o'zgaradi (hisoblagich, holat). `var` ishlatmang.
- **Literal tip** (`'CREATED' | 'PAID' | ...`) — faqat ruxsat etilgan qiymatlar. QA uchun oltin: status nomidagi xato darhol ushlanadi.
- TypeScript ko'pincha tipni o'zi aniqlaydi: `const price = 185_000` yozsangiz ham `number` bo'ladi.

TypeScript nimalarni ushlaydi (haqiqiy `tsc` xabarlari):

```typescript
let status: OrderStatus = "CREATED";
status = "PAYED";
// error TS2322: Type '"PAYED"' is not assignable to type 'OrderStatus'.

let quantity: number = 5;
quantity = "5";
// error TS2322: Type 'string' is not assignable to type 'number'.

const data: unknown = JSON.parse('{"total": 300000}');
console.log(data.total);
// error TS18046: 'data' is of type 'unknown'.
```

### `any` va `unknown`

- **`any`** — "tekshirma". TypeScript ko'zini yumadi. Testlarda deyarli ishlatmang.
- **`unknown`** — "nima ekanini bilmayman, avval tekshiraman". API javobi kabi tashqi ma'lumot uchun to'g'ri tanlov. Uni ishlatishdan oldin tipini aniqlash (narrowing) yoki validatsiya qilish kerak — buni [QA 20](/ios-lessons/qa-api-automation) da Zod bilan chiroyli qilamiz.

> **⚠️ Diqqat:** `as Booking` kabi "tip berish" (type assertion) TypeScript'ni ishontiradi, lekin **ma'lumotni tekshirmaydi**. Agar API boshqa shakldagi javob qaytarsa, `as` buni yashiradi. Shuning uchun API testlarida schema validatsiyasi kerak.

---

## 4. Interface va type: DemoBozor modellari {#interface}

Interface obyekt "shakli"ni tasvirlaydi. Test ma'lumotlari, API javoblari va page object'lar shular asosida quriladi.

```typescript
interface Product {
  id: number;
  name: string;
  price: number; // so'm
  stock: number;
  category?: string; // ? — ixtiyoriy maydon
}

interface User {
  readonly id: number; // o'zgartirib bo'lmaydi
  phone: string;
  fullName: string;
  role: "buyer" | "seller" | "admin";
  isPremium: boolean;
}

const kettle: Product = {
  id: 1,
  name: "Elektr choynak",
  price: 185_000,
  stock: 12,
};
const buyer: User = {
  id: 7,
  phone: "+998901234567",
  fullName: "Aziz Karimov",
  role: "buyer",
  isPremium: false,
};

// buyer.id = 8; // ❌ Cannot assign to 'id' because it is a read-only property.
console.log(kettle.category ?? "kategoriya yo'q", buyer.role); // kategoriya yo'q buyer
```

| `interface`                             | `type`                                                  |
| --------------------------------------- | ------------------------------------------------------- |
| Obyekt shakli uchun (`User`, `Product`) | Union, literal va kombinatsiyalar uchun (`OrderStatus`) |
| `extends` bilan kengaytiriladi          | `&` bilan birlashtiriladi                               |

Qoida sifatida: **obyektlar uchun `interface`, qolgani uchun `type`**.

---

## 5. Funksiyalar {#funksiyalar}

```typescript
const FREE_DELIVERY_THRESHOLD = 300_000;
const DELIVERY_FEE = 20_000;

// 1) oddiy funksiya: parametr va natija tiplari bilan
function calculateDeliveryFee(subtotal: number): number {
  if (subtotal >= FREE_DELIVERY_THRESHOLD) {
    return 0;
  }
  return DELIVERY_FEE;
}

// 2) arrow funksiya
const isFreeDelivery = (subtotal: number): boolean =>
  calculateDeliveryFee(subtotal) === 0;

// 3) default va ixtiyoriy parametrlar
function formatPrice(
  amount: number,
  currency = "so'm",
  withSpaces?: boolean,
): string {
  const digits = withSpaces
    ? String(amount).replace(/\B(?=(\d{3})+(?!\d))/g, " ")
    : String(amount);
  return `${digits} ${currency}`;
}

console.log(calculateDeliveryFee(299_999)); // 20000
console.log(calculateDeliveryFee(300_000)); // 0
console.log(isFreeDelivery(450_000)); // true
console.log(formatPrice(1_250_000)); // 1250000 so'm
console.log(formatPrice(1_250_000, "so'm", true)); // 1 250 000 so'm
```

E'tibor bering: `calculateDeliveryFee(299_999)` va `calculateDeliveryFee(300_000)` — bu [QA 5](/ios-lessons/qa-test-design-1) dagi **chegara qiymatlari tahlili (BVA)**. Agar dasturchi `>=` o'rniga `>` yozsa, aynan 300 000 so'mlik buyurtma uchun yetkazib berish pullik bo'lib qoladi — QA 2 dagi error → defect → failure misoli.

> **💡 Maslahat:** "Sehrli sonlar" (300000, 20000) o'rniga nomlangan konstantalar (`FREE_DELIVERY_THRESHOLD`) ishlating. Qoida o'zgarsa — bitta joyni tuzatasiz, test nomidan esa nimani tekshirayotgani tushunarli bo'ladi.

---

## 6. Shartlar, switch va sikllar {#shartlar}

Statuslarni o'zbekcha yorliqqa aylantiramiz va DemoBozor qoidasini kodlaymiz: buyurtmani faqat `CREATED`, `PENDING_PAYMENT`, `PAID` va `PACKING` holatlarida bekor qilish mumkin ([QA 6](/ios-lessons/qa-test-design-2) dagi state transition).

```typescript
// OrderStatus — 3-bo'limdagi tip
const STATUS_LABELS: Record<OrderStatus, string> = {
  CREATED: "Yaratildi",
  PENDING_PAYMENT: "To'lov kutilmoqda",
  PAID: "To'landi",
  PACKING: "Yig'ilmoqda",
  SHIPPED: "Yo'lda",
  DELIVERED: "Yetkazildi",
  CANCELLED: "Bekor qilindi",
  RETURNED: "Qaytarildi",
};

function statusLabel(status: OrderStatus): string {
  return STATUS_LABELS[status];
}

function canCancel(status: OrderStatus): boolean {
  switch (status) {
    case "CREATED":
    case "PENDING_PAYMENT":
    case "PAID":
    case "PACKING":
      return true;
    default:
      return false;
  }
}

const statuses: OrderStatus[] = ["CREATED", "PAID", "SHIPPED"];
for (const s of statuses) {
  console.log(
    `${s} -> ${statusLabel(s)} | bekor qilish mumkinmi: ${canCancel(s)}`,
  );
}
// CREATED -> Yaratildi | bekor qilish mumkinmi: true
// PAID -> To'landi | bekor qilish mumkinmi: true
// SHIPPED -> Yo'lda | bekor qilish mumkinmi: false
```

`Record<OrderStatus, string>` — "har bir status uchun yorliq bo'lishi **shart**" degani. Ertaga dasturchi `REFUNDED` statusini qo'shsa-yu, yorlig'ini yozishni unutsa, `tsc` darhol ogohlantiradi:

```text
error TS2741: Property 'REFUNDED' is missing in type '{ ... }' but required in type 'Record<OrderStatus, string>'.
```

`switch`dagi bir nechta `case` ketma-ket yozilsa, ular bitta natijani bo'lishadi. `for...of` esa massivni elementma-element aylanib chiqadi — testlarda ma'lumotlar ro'yxati bo'yicha bir xil tekshiruvni takrorlash uchun eng ko'p ishlatiladigan sikl.

### Ikki klassik tuzoq

**1. Truthy/falsy.** JavaScript'da `0`, `''`, `null`, `undefined`, `NaN` — "yolg'on" (falsy):

```typescript
const quantity = 0;
if (quantity) {
  console.log("miqdor bor");
} else {
  console.log("miqdor yo'q (0 falsy!)"); // shu chiqadi
}
```

Agar "miqdor kiritilganmi?" degan tekshiruv `if (quantity)` bilan yozilsa, `0` qiymati "kiritilmagan" deb hisoblanadi — bu haqiqiy bug manbai. To'g'risi: `if (quantity !== undefined)`.

**2. `==` va `===`.** JavaScript'da `'0' == 0` → `true` (tipni o'zgartirib solishtiradi). Har doim `===` ishlating. TypeScript bunday solishtirishni o'zi ham to'xtatadi: `This comparison appears to be unintentional because the types 'string' and 'number' have no overlap.`

---

## 7. Massivlar va obyektlar {#massivlar}

Test kodining katta qismi — ro'yxatlar bilan ishlash: UI'dan o'qilgan narxlar, API qaytargan mahsulotlar, savatcha qatorlari.

```typescript
interface Product {
  id: number;
  name: string;
  price: number;
  stock: number;
}

const products: Product[] = [
  { id: 1, name: "Elektr choynak", price: 185_000, stock: 12 },
  { id: 2, name: "Paxta ko'ylak", price: 95_000, stock: 0 },
  { id: 3, name: "Simsiz quloqchin", price: 420_000, stock: 5 },
  { id: 4, name: "Gilam 2x3 m", price: 1_200_000, stock: 2 },
  { id: 5, name: "Telefon g'ilofi", price: 45_000, stock: 40 },
];

const names = products.map((p) => p.name); // har biridan nom
const outOfStock = products.filter((p) => p.stock === 0); // shartga mos kelganlar
const headphones = products.find((p) => p.name.includes("quloqchin")); // birinchi mos kelgan
const hasExpensive = products.some((p) => p.price > 1_000_000); // kamida bittasi?
const allHavePrice = products.every((p) => p.price > 0); // hammasi?

console.log(
  names.length,
  outOfStock.map((p) => p.name),
  headphones?.price,
  hasExpensive,
  allHavePrice,
);
// 5 [ "Paxta ko'ylak" ] 420000 true true
```

| Metod            | Qaytaradi                         | QA'dagi odatiy ishlatilishi                           |
| ---------------- | --------------------------------- | ----------------------------------------------------- |
| `map`            | Yangi massiv                      | UI matnlarini songa aylantirish: `'$29.99'` → `29.99` |
| `filter`         | Mos kelganlar massivi             | Tugagan mahsulotlar, xato statusdagi buyurtmalar      |
| `find`           | Birinchi element yoki `undefined` | ID bo'yicha mahsulot topish                           |
| `some` / `every` | `boolean`                         | "Hech bo'lmasa bittasi..." / "hammasi..." tekshiruvi  |
| `reduce`         | Bitta qiymat                      | Savatcha jami summasi                                 |

### Savatcha summasi va saralash tekshiruvi

```typescript
const cart = [
  { productId: 1, quantity: 1 },
  { productId: 5, quantity: 3 },
];
const subtotal = cart.reduce((sum, item) => {
  const product = products.find((p) => p.id === item.productId);
  return sum + (product ? product.price * item.quantity : 0);
}, 0);
console.log("subtotal:", subtotal, "| bepul yetkazish:", subtotal >= 300_000);
// subtotal: 320000 | bepul yetkazish: true

// UI'da "arzonidan qimmatiga" saralash to'g'rimi?
const uiPrices = [45_000, 95_000, 185_000, 420_000, 1_200_000];
const isSortedAsc = uiPrices.every(
  (price, i) => i === 0 || uiPrices[i - 1] <= price,
);
console.log("o'sish tartibida:", isSortedAsc); // true

// Takrorlanuvchi ID'lar (dublikat buyurtmalar bug'i)
const orderIds = [101, 102, 103, 102, 104, 101];
const duplicates = orderIds.filter((id, i) => orderIds.indexOf(id) !== i);
console.log("dublikatlar:", [...new Set(duplicates)]); // [ 102, 101 ]
```

### Obyektlar: spread, destructuring, `?.` va `??`

```typescript
interface TestUser {
  phone: string;
  fullName: string;
  isPremium: boolean;
  address?: { city: string; street?: string };
}

const baseUser: TestUser = {
  phone: "+998901234567",
  fullName: "Aziz Karimov",
  isPremium: false,
};

// spread: asosiy ma'lumotdan test varianti yasash
const premiumUser = { ...baseUser, isPremium: true };
const longNameUser = { ...baseUser, fullName: "A".repeat(51) }; // 51 belgi — chegaradan oshgan

// destructuring
const { phone, fullName } = premiumUser;

// optional chaining va nullish coalescing
console.log(baseUser.address?.city ?? "manzil kiritilmagan"); // manzil kiritilmagan

// || va ?? farqi: 0 — haqiqiy qiymat!
const deliveryFeeFromApi = 0;
console.log("|| bilan:", deliveryFeeFromApi || 20_000); // 20000 — BUG
console.log("?? bilan:", deliveryFeeFromApi ?? 20_000); // 0 — to'g'ri
```

> **⚠️ Diqqat:** `||` chap tomondagi har qanday falsy qiymatni (shu jumladan `0` va `''`) "yo'q" deb hisoblaydi. API `deliveryFee: 0` qaytarganda `||` uni 20 000 ga almashtirib yuboradi. Standart qiymat berishda **`??`** ishlating — u faqat `null` va `undefined` ni almashtiradi.

---

## 8. Modullar va npm {#modullar}

Kod bitta faylda qolib ketmasligi uchun modullarga bo'linadi. `export` — tashqariga beramiz, `import` — olib kelamiz.

`src/pricing.ts`:

```typescript
export const FREE_DELIVERY_THRESHOLD = 300_000;
export const DELIVERY_FEE = 20_000;

export function calculateDeliveryFee(subtotal: number): number {
  return subtotal >= FREE_DELIVERY_THRESHOLD ? 0 : DELIVERY_FEE;
}
```

`src/main.ts`:

```typescript
import { calculateDeliveryFee, FREE_DELIVERY_THRESHOLD } from "./pricing";

console.log(
  `Chegara: ${FREE_DELIVERY_THRESHOLD}, 150000 uchun: ${calculateDeliveryFee(150_000)}`,
);
// Chegara: 300000, 150000 uchun: 20000
```

> **💡 Maslahat:** **Named export** (`export function ...`) ishlating, `export default` emas. Nomi bilan import qilinadi, qayta nomlash xavfsizroq, IDE uni oson topadi.

### npm asoslari

| Buyruq / fayl       | Vazifasi                                                                       |
| ------------------- | ------------------------------------------------------------------------------ |
| `package.json`      | Loyiha pasporti: nomi, script'lar, bog'liqliklar                               |
| `npm i -D <paket>`  | Paketni `devDependencies`ga qo'shadi (testlar uchun asboblar odatda shu yerda) |
| `npx <buyruq>`      | Lokal o'rnatilgan asbobni ishga tushiradi (`npx tsc`, `npx playwright test`)   |
| `package-lock.json` | Aniq versiyalarni "muzlatadi". **Git'ga commit qiling**                        |
| `npm ci`            | Lock fayldan aynan bir xil o'rnatish — CI uchun                                |
| `node_modules/`     | O'rnatilgan paketlar. **Git'ga qo'shmang** (`.gitignore`)                      |
| `"^1.64.0"`         | 1.64.0 va undan yuqori, lekin 2.0 dan past versiyalar                          |

`package.json`dagi script'lar uzun buyruqlarga qisqa nom beradi:

```json
{
  "scripts": {
    "typecheck": "tsc",
    "smoke": "tsx src/project/smoke.ts"
  }
}
```

Endi `npm run typecheck` va `npm run smoke` yetarli.

---

## 9. Class'lar — Page Object poydevori {#classlar}

Class — ma'lumot (property) va xatti-harakat (method)ni bir joyga yig'adigan "qolip". [QA 19](/ios-lessons/qa-playwright-advanced) dagi Page Object Model aynan class'lar ustiga quriladi.

```typescript
interface CartLine {
  productId: number;
  price: number;
  quantity: number;
}

class Cart {
  static readonly MAX_QUANTITY = 99;
  static readonly MAX_DISTINCT_PRODUCTS = 50;

  private readonly lines = new Map<number, CartLine>();

  constructor(private readonly ownerPhone: string) {}

  addItem(productId: number, price: number, quantity = 1): void {
    if (quantity < 1 || quantity > Cart.MAX_QUANTITY) {
      throw new Error(
        `Miqdor 1–${Cart.MAX_QUANTITY} oralig'ida bo'lishi kerak, keldi: ${quantity}`,
      );
    }
    if (
      !this.lines.has(productId) &&
      this.lines.size >= Cart.MAX_DISTINCT_PRODUCTS
    ) {
      throw new Error(
        `Savatchada ko'pi bilan ${Cart.MAX_DISTINCT_PRODUCTS} xil mahsulot bo'ladi`,
      );
    }
    this.lines.set(productId, { productId, price, quantity });
  }

  get subtotal(): number {
    let sum = 0;
    for (const line of this.lines.values()) {
      sum += line.price * line.quantity;
    }
    return sum;
  }

  get deliveryFee(): number {
    return this.subtotal >= 300_000 ? 0 : 20_000;
  }

  get owner(): string {
    return this.ownerPhone;
  }
}

const cart = new Cart("+998901234567");
cart.addItem(1, 185_000);
cart.addItem(5, 45_000, 3);
console.log(cart.owner, cart.subtotal, cart.deliveryFee); // +998901234567 320000 0

try {
  cart.addItem(2, 95_000, 100);
} catch (error) {
  console.log("Kutilgan xato:", (error as Error).message);
  // Kutilgan xato: Miqdor 1–99 oralig'ida bo'lishi kerak, keldi: 100
}
```

| Kalit so'z                                         | Ma'nosi                                                         |
| -------------------------------------------------- | --------------------------------------------------------------- |
| `constructor(private readonly ownerPhone: string)` | Parametr avtomatik ravishda maxfiy, o'zgarmas property bo'ladi  |
| `private`                                          | Faqat class ichidan ko'rinadi                                   |
| `readonly`                                         | Bir marta beriladi, keyin o'zgarmaydi                           |
| `static`                                           | Obyektga emas, class'ning o'ziga tegishli (`Cart.MAX_QUANTITY`) |
| `get subtotal()`                                   | Property kabi o'qiladi: `cart.subtotal`                         |

Aynan shu g'oya — ma'lumot va harakatni bitta class'ga yig'ish — [QA 19](/ios-lessons/qa-playwright-advanced) dagi Page Object'larning asosi: u yerda `LoginPage`, `InventoryPage` kabi class'lar locator'lar va sahifa action'larini saqlaydi.

> **💡 Maslahat:** Page object'lar uchun **meros (inheritance)** o'rniga **kompozitsiya**ni afzal ko'ring: `BasePage → CatalogPage → ProductPage → ...` kabi uzun zanjir tez chalkashadi. Umumiy qismni (masalan, header) alohida class qilib, sahifaga property sifatida bering.

---

## 10. Asinxron kod: Promise va async/await {#async}

Brauzer, tarmoq va fayl bilan ishlash **vaqt oladi**. JavaScript bu vaqtda "qotib qolmaydi" — natijani keyinroq beradigan **Promise** (va'da) qaytaradi. `await` esa "natija kelguncha shu yerda kutib tur" degani.

Playwright'dagi deyarli hamma narsa asinxron: `page.goto()`, `click()`, `fill()`, `expect(...).toBeVisible()`. Shuning uchun `async/await`ni tushunmasdan avtotest yozib bo'lmaydi.

```typescript
const sleep = (ms: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, ms));

async function checkService(name: string, ms: number): Promise<string> {
  await sleep(ms); // tarmoq so'rovini taqlid qilamiz
  return `${name}: OK`;
}

// 1) ketma-ket — har biri oldingisini kutadi
await checkService("catalog", 300);
await checkService("cart", 300);
await checkService("orders", 300); // jami ~900 ms

// 2) parallel — uchalasi bir vaqtda
const results = await Promise.all([
  checkService("catalog", 300),
  checkService("cart", 300),
  checkService("orders", 300),
]); // jami ~300 ms
console.log(results); // [ 'catalog: OK', 'cart: OK', 'orders: OK' ]
```

> **🧠 Esda tuting:** Bu yerda `sleep` faqat sekin tarmoqni **taqlid qilish** uchun. Haqiqiy testlarda "N soniya kutish" yozilmaydi — Playwright kerakli holatni o'zi kutadi ([QA 18](/ios-lessons/qa-playwright-basics)).

### Unutilgan `await` — beqaror testlarning klassik sababi

![await unutilganda va qo'yilganda bajarilish tartibi](/lessons/images/qa/qa_17_async_flow.svg)

```typescript
async function saveOrder(): Promise<void> {
  await sleep(200);
  console.log("  buyurtma bazaga saqlandi");
}

async function wrongOrder(): Promise<void> {
  saveOrder(); // ❌ await unutildi
  console.log("  tekshiruv: buyurtma bormi?");
}

async function rightOrder(): Promise<void> {
  await saveOrder(); // ✅
  console.log("  tekshiruv: buyurtma bormi?");
}
```

Haqiqiy natija:

```text
await yo'q:
  tekshiruv: buyurtma bormi?      ← tekshiruv saqlashdan OLDIN ishladi
  buyurtma bazaga saqlandi
await bor:
  buyurtma bazaga saqlandi
  tekshiruv: buyurtma bormi?
```

Testda bu "ba'zan o'tadi, ba'zan yo'q" degan flaky test bo'ladi: tarmoq tez bo'lsa omad kulib boqadi, sekin bo'lsa — yo'q. Playwright 1.64 da `await`siz yozilgan `expect(...)` test tugaguncha bajarilib ulgurmasa, test `Error: Test ended.` xatosi bilan yiqiladi (biz tekshirdik), lekin har qanday unutilgan `await`ni bunday ushlab bo'lmaydi — masalan, `await`siz `click()` shunchaki poyga holatini (race condition) keltirib chiqaradi. Playwright'ning rasmiy tavsiyasi — **`@typescript-eslint/no-floating-promises`** lint qoidasini yoqish: u qayta ishlanmagan Promise'ni ko'rsatib beradi.

### Xatolarni ushlash: try / catch / finally

```typescript
async function fetchOrder(id: number): Promise<{ id: number; status: string }> {
  await sleep(100);
  if (id <= 0) {
    throw new Error(`Noto'g'ri buyurtma ID: ${id}`);
  }
  return { id, status: "PAID" };
}

try {
  await fetchOrder(-1);
} catch (error) {
  console.log("ushlandi:", (error as Error).message); // ushlandi: Noto'g'ri buyurtma ID: -1
} finally {
  console.log("finally: test ma'lumotlarini tozalash"); // har doim ishlaydi
}
```

`finally` — test ma'lumotlarini tozalash uchun ideal joy: test yiqilsa ham ishlaydi.

> **🎯 Intervyuda:** "`Promise.all` qachon kerak?" — Bir-biriga bog'liq bo'lmagan bir nechta asinxron ishni parallel bajarish uchun: masalan, testdan oldin API orqali 3 ta mahsulotni bir vaqtda yaratish. Ulardan biri xato bersa, `Promise.all` ham xato bilan tugaydi; hammasining natijasi kerak bo'lsa — `Promise.allSettled`.

---

## 11. fetch: API'ga birinchi so'rov {#fetch}

Node.js'da `fetch` o'rnatilgan — paket kerak emas. Mashq uchun API testlash uchun mo'ljallangan [Restful-Booker](https://restful-booker.herokuapp.com/apidoc/index.html) xizmatidan foydalanamiz:

```typescript
const BASE_URL = "https://restful-booker.herokuapp.com";

interface BookingDates {
  checkin: string; // YYYY-MM-DD
  checkout: string;
}

interface Booking {
  firstname: string;
  lastname: string;
  totalprice: number;
  depositpaid: boolean;
  bookingdates: BookingDates;
  additionalneeds?: string;
}

interface CreatedBooking {
  bookingid: number;
  booking: Booking;
}

async function createBooking(booking: Booking): Promise<CreatedBooking> {
  const response = await fetch(`${BASE_URL}/booking`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Accept: "application/json" },
    body: JSON.stringify(booking),
    signal: AbortSignal.timeout(10_000), // 10 soniyadan ko'p kutmaymiz
  });
  if (!response.ok) {
    throw new Error(`POST /booking: kutilmagan status ${response.status}`);
  }
  return (await response.json()) as CreatedBooking;
}

const newBooking: Booking = {
  firstname: "Aziz",
  lastname: "Karimov",
  totalprice: 150,
  depositpaid: true,
  bookingdates: { checkin: "2026-11-01", checkout: "2026-11-05" },
  additionalneeds: "Nonushta",
};

const created = await createBooking(newBooking);
console.log("yaratildi:", typeof created.bookingid === "number"); // yaratildi: true

const response = await fetch(`${BASE_URL}/booking/${created.bookingid}`, {
  headers: { Accept: "application/json" },
});
const saved = (await response.json()) as Booking;
console.log(
  response.status,
  saved.firstname === newBooking.firstname ? "ism mos" : "ism MOS EMAS",
);
// 200 ism mos
```

Muhim nuqtalar:

- `response.ok` — status 200–299 oralig'ida bo'lsa `true`. Har doim tekshiring.
- `AbortSignal.timeout(10_000)` — server javob bermasa, abadiy kutmaymiz.
- `await response.json()` — tana (body) ham asinxron o'qiladi.

> **🎯 Intervyuda:** "Restful-Booker'da noto'g'ri parol bilan `POST /auth` qanday status qaytaradi?" — Biz tekshirdik: **200** va `{"reason":"Bad credentials"}`. REST odatlariga ko'ra bu yerda **401** kutiladi. Yangi bron yaratish (`POST /booking`) esa **201** emas, **200** qaytaradi. Bunday "qiziq" javoblarni topish va muhokama qilish — QA'ning kundalik ishi ([QA 10](/ios-lessons/qa-api-postman)).

---

## 12. Regex, sanalar va muhit o'zgaruvchilari {#regex-sana-env}

### Regex: formatlarni tekshirish

```typescript
const UZ_PHONE = /^\+998\d{9}$/;
const PROMO_CODE = /^[A-Z0-9]{6,12}$/;
const OTP_CODE = /^\d{6}$/;

function isValidUzPhone(raw: string): boolean {
  const normalized = raw.replace(/[\s-]/g, ""); // probel va defislarni olib tashlaymiz
  return UZ_PHONE.test(normalized);
}

console.log(isValidUzPhone("+998 90 123 45 67")); // true
console.log(isValidUzPhone("998901234567")); // false — "+" yo'q
console.log(isValidUzPhone("+99890123456")); // false — 8 ta raqam
console.log(
  PROMO_CODE.test("BAHOR2026"),
  PROMO_CODE.test("bahor2026"),
  PROMO_CODE.test("A1B2C"),
);
// true false false
console.log(
  OTP_CODE.test("123456"),
  OTP_CODE.test("12345"),
  OTP_CODE.test("12a456"),
);
// true false false

// ^ va $ bo'lmasa regex matnning BIR QISMINI ham "to'g'ri" deb hisoblaydi
console.log(/\d{6}/.test("1234567"), /^\d{6}$/.test("1234567")); // true false
```

> **⚠️ Diqqat:** Validatsiya regex'ida `^` (boshi) va `$` (oxiri) unutilsa, 7 xonali SMS kod ham "6 xonali" deb qabul qilinadi. Dasturchi kodida shunday regex'ni ko'rsangiz — bu bug.

### Sanalar: UTC va Toshkent vaqti

```typescript
// Sana ichkarida UTC'da saqlanadi, foydalanuvchiga — Toshkent vaqti (UTC+5)
const orderTime = new Date("2026-10-09T20:30:00Z");
const tashkent = new Intl.DateTimeFormat("en-GB", {
  timeZone: "Asia/Tashkent",
  dateStyle: "short",
  timeStyle: "short",
});
console.log(orderTime.toISOString()); // 2026-10-09T20:30:00.000Z
console.log(tashkent.format(orderTime)); // 10/10/2026, 01:30 — Toshkentda allaqachon ertangi kun!

// Oylar 0 dan boshlanadi: 9 = oktabr
console.log(new Date(2026, 9, 9).getMonth()); // 9
```

UTC bo'yicha 9-oktabr kechqurun bo'lgan buyurtma Toshkentda **10-oktabr** sanasiga tushadi. "Bugungi buyurtmalar" hisobotida buyurtma ko'rinmay qolsa — avval vaqt zonasini tekshiring. Kunning oxiri (19:00–24:00 UTC) atrofidagi holatlar — vaqt bilan bog'liq bug'larning sevimli joyi. Lokalizatsiya va sana formatlari haqida batafsil — [QA 25](/ios-lessons/qa-a11y-l10n-visual).

### Muhit o'zgaruvchilari va `.env`

Test kodida URL va parollar qattiq yozilmaydi — ular muhitdan olinadi:

```bash
npm i -D dotenv
```

`.env` (git'ga qo'shilmaydi!):

```text
BASE_URL=https://www.saucedemo.com
API_BASE_URL=https://restful-booker.herokuapp.com
```

```typescript
import "dotenv/config";

const baseUrl = process.env.BASE_URL ?? "https://www.saucedemo.com";
const apiBaseUrl = process.env.API_BASE_URL;

if (!apiBaseUrl) {
  throw new Error("API_BASE_URL .env faylida berilmagan");
}
console.log(baseUrl, apiBaseUrl);
```

Node.js'ning o'zida ham imkoniyat bor: `npx tsx --env-file=.env src/app.ts` yoki kod ichida `process.loadEnvFile()` — ikkalasi ham Node 24 da tekshirildi.

> **⚠️ Diqqat:** `.env` faylni hech qachon git'ga commit qilmang. Uning o'rniga qiymatlarsiz `.env.example` saqlang, haqiqiy maxfiy ma'lumotlar esa CI'da secret sifatida beriladi ([QA 21](/ios-lessons/qa-ci-cd)).

---

## 13. faker bilan test ma'lumotlari {#faker}

Har testda "Test Testov" yozish o'rniga, realistik va har safar yangi ma'lumot yaratamiz:

```bash
npm i -D @faker-js/faker
```

```typescript
import { fakerUZ_UZ_latin as faker } from "@faker-js/faker";

faker.seed(2026); // bir xil seed — har safar bir xil ma'lumot

function randomUzPhone(): string {
  const operator = faker.helpers.arrayElement([
    "90",
    "91",
    "93",
    "94",
    "97",
    "99",
  ]);
  return `+998${operator}${faker.string.numeric(7)}`;
}

const users = Array.from({ length: 3 }, () => ({
  fullName: `${faker.person.firstName()} ${faker.person.lastName()}`,
  phone: randomUzPhone(),
  promoCode: faker.string.alphanumeric({ length: 8, casing: "upper" }),
}));

for (const user of users) {
  console.log(user);
}
// { fullName: 'Iroda Miranov', phone: '+998991957332', promoCode: 'W1GKDPX1' }
// ... (faker 10.6.0 da seed 2026 bilan)
```

Biz tekshirganda topgan uchta foydali fakt:

1. **Seed** (`faker.seed(2026)`) bilan natija takrorlanadi — yiqilgan testni xuddi shu ma'lumot bilan qayta ishga tushirish mumkin. (Faker versiyasi o'zgarsa, natija ham o'zgarishi mumkin.)
2. **O'zbek lokali** (`fakerUZ_UZ_latin`) ism-familiyalarni o'zbekcha beradi, lekin `phone.number()` o'zbek formatida emas (`783.229.2490 x69259` kabi chiqdi). Shuning uchun `+998` raqamni o'zimiz yasadik.
3. `faker.helpers.fromRegExp()` murakkab regex'larni (masalan, `(90|91)` kabi guruhlarni) to'liq tushunmaydi — natijada `\+998(90|91|...)` matnining o'zi chiqib qoldi. Oddiy `arrayElement` + `string.numeric` ishonchliroq.

> **⚠️ Diqqat:** Tasodifiy yaratilgan `+998...` raqam **haqiqiy odamniki** bo'lishi mumkin. Test muhitida real SMS yuborilmasligiga ishonch hosil qiling: SMS provayderning sandbox rejimi yoki dasturchilar bilan kelishilgan test raqamlar ishlatilsin.

---

## 14. Mini-loyiha: API smoke skripti {#mini-loyiha}

Endi o'rganganlarni birlashtiramiz: validatorlar + test ma'lumotlari + asinxron API tekshiruvlari. Natija — CI'da ishlay oladigan kichik smoke skript.

```text
qa-ts-basics/
└── src/project/
    ├── validators.ts   # format tekshiruvlari
    ├── test-data.ts    # faker bilan ma'lumot
    └── smoke.ts        # API smoke tekshiruvlari
```

`src/project/validators.ts`:

```typescript
const UZ_PHONE = /^\+998\d{9}$/;
const PROMO_CODE = /^[A-Z0-9]{6,12}$/;
const OTP_CODE = /^\d{6}$/;

export function isValidUzPhone(raw: string): boolean {
  return UZ_PHONE.test(raw.replace(/[\s-]/g, ""));
}

export function isValidPromoCode(code: string): boolean {
  return PROMO_CODE.test(code);
}

export function isValidOtp(code: string): boolean {
  return OTP_CODE.test(code);
}
```

`src/project/test-data.ts`:

```typescript
import { fakerUZ_UZ_latin as faker } from "@faker-js/faker";

export interface TestBooking {
  firstname: string;
  lastname: string;
  totalprice: number;
  depositpaid: boolean;
  bookingdates: { checkin: string; checkout: string };
  additionalneeds: string;
}

export function useSeed(seed: number): void {
  faker.seed(seed);
}

export function randomUzPhone(): string {
  const operator = faker.helpers.arrayElement([
    "90",
    "91",
    "93",
    "94",
    "97",
    "99",
  ]);
  return `+998${operator}${faker.string.numeric(7)}`;
}

export function createBookingData(
  overrides: Partial<TestBooking> = {},
): TestBooking {
  return {
    firstname: faker.person.firstName(),
    lastname: faker.person.lastName(),
    totalprice: faker.number.int({ min: 50, max: 500 }),
    depositpaid: faker.datatype.boolean(),
    bookingdates: { checkin: "2026-11-01", checkout: "2026-11-05" },
    additionalneeds: "Nonushta",
    ...overrides, // testga kerakli maydonlarni almashtirish
  };
}
```

`src/project/smoke.ts`:

```typescript
import { createBookingData, randomUzPhone, useSeed } from "./test-data";
import { isValidUzPhone } from "./validators";

const BASE_URL =
  process.env.API_BASE_URL ?? "https://restful-booker.herokuapp.com";
const TIMEOUT_MS = 10_000;

type Check = () => Promise<string>;

async function run(name: string, check: Check): Promise<boolean> {
  try {
    const details = await check();
    console.log(`✅ ${name} — ${details}`);
    return true;
  } catch (error) {
    console.log(`❌ ${name} — ${(error as Error).message}`);
    return false;
  }
}

function expectStatus(response: Response, expected: number): void {
  if (response.status !== expected) {
    throw new Error(`kutilgan status ${expected}, keldi ${response.status}`);
  }
}

async function post(path: string, body: unknown): Promise<Response> {
  return fetch(`${BASE_URL}${path}`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Accept: "application/json" },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(TIMEOUT_MS),
  });
}

useSeed(2026);
const booking = createBookingData();

const results = [
  await run("Test ma'lumoti: telefon formati", async () => {
    const phone = randomUzPhone();
    if (!isValidUzPhone(phone)) throw new Error(`yaroqsiz: ${phone}`);
    return phone;
  }),
  await run("GET /ping", async () => {
    const response = await fetch(`${BASE_URL}/ping`, {
      signal: AbortSignal.timeout(TIMEOUT_MS),
    });
    expectStatus(response, 201);
    return "servis ishlayapti";
  }),
  await run("POST /auth (to'g'ri login)", async () => {
    const response = await post("/auth", {
      username: "admin",
      password: "password123",
    });
    expectStatus(response, 200);
    const body = (await response.json()) as { token?: string };
    if (!body.token) throw new Error("token qaytmadi");
    return "token olindi";
  }),
  await run("POST /auth (noto'g'ri parol)", async () => {
    const response = await post("/auth", {
      username: "admin",
      password: "xato",
    });
    const body = (await response.json()) as { token?: string; reason?: string };
    if (body.token) throw new Error("noto'g'ri parol bilan token berildi!");
    return `token yo'q, status ${response.status}, sabab: ${body.reason}`;
  }),
  await run("POST /booking + GET /booking/:id", async () => {
    const created = await post("/booking", booking);
    expectStatus(created, 200);
    const { bookingid } = (await created.json()) as { bookingid: number };

    const response = await fetch(`${BASE_URL}/booking/${bookingid}`, {
      headers: { Accept: "application/json" },
      signal: AbortSignal.timeout(TIMEOUT_MS),
    });
    expectStatus(response, 200);
    const saved = (await response.json()) as typeof booking;
    if (saved.lastname !== booking.lastname)
      throw new Error("familiya mos emas");
    return `bron #${bookingid} saqlandi`;
  }),
];

const failed = results.filter((ok) => !ok).length;
console.log(`\nNatija: ${results.length - failed} o'tdi, ${failed} yiqildi`);
if (failed > 0) process.exitCode = 1; // CI uchun: xato bo'lsa nol bo'lmagan kod
```

Ishga tushiramiz:

```bash
npx tsx src/project/smoke.ts
```

```text
✅ Test ma'lumoti: telefon formati — +998995733280
✅ GET /ping — servis ishlayapti
✅ POST /auth (to'g'ri login) — token olindi
✅ POST /auth (noto'g'ri parol) — token yo'q, status 200, sabab: Bad credentials
✅ POST /booking + GET /booking/:id — bron #1814 saqlandi

Natija: 5 o'tdi, 0 yiqildi
```

Bu skriptda keyingi darslarning urug'lari bor: tekshiruvlarni nomlash, xatolarni ushlash, test ma'lumotini yaratish, `process.exitCode` orqali CI'ga signal berish. [QA 20](/ios-lessons/qa-api-automation) da xuddi shu ishni Playwright `request` va Zod bilan professional darajada qilamiz.

---

## 15. Amaliy mashqlar {#mashqlar}

Har bir javob TypeScript 7.0.2 + tsx da ishga tushirib tekshirilgan.

### Mashq 1: Xatoni oldindan ko'ring

`tsc` quyidagi kod uchun qanday xatolar beradi?

```typescript
interface OrderItem {
  productId: number;
  quantity: number;
  unitPrice: number;
}

const item: OrderItem = { productId: 3, quantity: "2", unitPrice: 420_000 };

function lineTotal(line: OrderItem): number {
  return line.quantity * line.unitPrice;
}

lineTotal({ productId: 1, quantity: 1 });
```

<details>
<summary>💡 Javobni ko'rish</summary>

```text
error TS2322: Type 'string' is not assignable to type 'number'.
error TS2741: Property 'unitPrice' is missing in type '{ productId: number; quantity: number; }' but required in type 'OrderItem'.
```

1. `quantity: '2'` — matn, son kutilgan.
2. `lineTotal(...)` ga `unitPrice` berilmagan — majburiy maydon.

Ikkala xato ham test **ishga tushishidan oldin** topildi — TypeScript'ning asosiy foydasi shu.

</details>

### Mashq 2: Ism validatori

DemoBozor qoidasi: ism **2–50 belgi**; faqat lotin yoki kirill harflari, probel, apostroflar (`ʻ`, `'`, `’`) va defis. `isValidName(name: string): boolean` yozing va chegaraviy qiymatlar bilan tekshiring.

<details>
<summary>💡 Javobni ko'rish</summary>

```typescript
const NAME = /^[\p{Script=Latin}\p{Script=Cyrillic} 'ʻ’-]{2,50}$/u;

export function isValidName(name: string): boolean {
  return NAME.test(name);
}

const cases: Array<[string, boolean]> = [
  ["Aziz", true],
  ["Oʻgʻiloy", true],
  ["G'ulom", true],
  ["Анна-Мария", true],
  ["A", false], // 1 belgi — chegaradan past
  ["A".repeat(50), true], // 50 — chegarada
  ["A".repeat(51), false], // 51 — chegaradan oshdi
  ["Aziz2", false], // raqam
  ["Aziz!", false], // ruxsat etilmagan belgi
];

for (const [input, expected] of cases) {
  const actual = isValidName(input);
  console.log(
    actual === expected ? "✅" : "❌",
    `"${input.slice(0, 12)}" (uzunlik ${input.length}) -> ${actual}`,
  );
}
```

Barcha 9 holat ✅. E'tibor bering: `u` bayrog'i va `\p{Script=Latin}` Unicode harflarini to'g'ri taniydi, `Oʻgʻiloy` dagi `ʻ` (U+02BB) belgisi esa ro'yxatga alohida qo'shilgan.

</details>

### Mashq 3: Buyurtma summasi

DemoBozor qoidalari bo'yicha `calculateOrder()` yozing: yangi mijozga 10%, promo-kod `promoPercent` foiz; chegirmalar qo'shilmaydi — kattasi olinadi; Premium a'zoga yetkazib berish har doim bepul; aks holda mahsulotlar summasi (chegirmagacha) ≥ 300 000 bo'lsa bepul, bo'lmasa 20 000 so'm. Natija: `{ discount, deliveryFee, total }`.

Tekshiring: (250 000, yangi), (400 000, yangi, promo 15%), (200 000, promo 15%, premium), (300 000, oddiy).

<details>
<summary>💡 Javobni ko'rish</summary>

```typescript
interface OrderInput {
  subtotal: number; // mahsulotlar summasi, so'm
  isNewCustomer: boolean;
  promoPercent?: number; // masalan 15
  isPremium: boolean;
}

interface OrderSummary {
  discount: number;
  deliveryFee: number;
  total: number;
}

export function calculateOrder(input: OrderInput): OrderSummary {
  const newCustomerPercent = input.isNewCustomer ? 10 : 0;
  const percent = Math.max(newCustomerPercent, input.promoPercent ?? 0); // chegirmalar qo'shilmaydi
  const discount = Math.round((input.subtotal * percent) / 100);
  const deliveryFee = input.isPremium || input.subtotal >= 300_000 ? 0 : 20_000;
  return {
    discount,
    deliveryFee,
    total: input.subtotal - discount + deliveryFee,
  };
}

console.log(
  calculateOrder({ subtotal: 250_000, isNewCustomer: true, isPremium: false }),
);
// { discount: 25000, deliveryFee: 20000, total: 245000 }
console.log(
  calculateOrder({
    subtotal: 400_000,
    isNewCustomer: true,
    promoPercent: 15,
    isPremium: false,
  }),
);
// { discount: 60000, deliveryFee: 0, total: 340000 }
console.log(
  calculateOrder({
    subtotal: 200_000,
    isNewCustomer: false,
    promoPercent: 15,
    isPremium: true,
  }),
);
// { discount: 30000, deliveryFee: 0, total: 170000 }
console.log(
  calculateOrder({ subtotal: 300_000, isNewCustomer: false, isPremium: false }),
);
// { discount: 0, deliveryFee: 0, total: 300000 }
```

QA savoli: talabda "bepul yetkazib berish chegarasi chegirmagacha yoki chegirmadan keyingi summaga qo'llanadimi?" deb aniq yozilmagan bo'lsa — bu **talabdagi noaniqlik**, uni kod yozishdan oldin aniqlashtirish kerak ([QA 3](/ios-lessons/qa-sdlc-stlc-agile)).

</details>

### Mashq 4: Massiv metodlari

[7-bo'limdagi](#massivlar) `products` ro'yxatidan toping: (a) tugagan mahsulotlar nomlari, (b) ombordagi jami qiymat (`price × stock` yig'indisi), (c) eng qimmat mahsulot nomi.

<details>
<summary>💡 Javobni ko'rish</summary>

```typescript
const outOfStockNames = products
  .filter((p) => p.stock === 0)
  .map((p) => p.name);
const inventoryValue = products.reduce((sum, p) => sum + p.price * p.stock, 0);
const mostExpensive = products.reduce((max, p) =>
  p.price > max.price ? p : max,
);

console.log(outOfStockNames); // [ "Paxta ko'ylak" ]
console.log(inventoryValue); // 8520000
console.log(mostExpensive.name); // Gilam 2x3 m
```

Hisob: 185 000×12 + 95 000×0 + 420 000×5 + 1 200 000×2 + 45 000×40 = 2 220 000 + 0 + 2 100 000 + 2 400 000 + 1 800 000 = **8 520 000**.

</details>

### Mashq 5: Saralash tekshiruvi

Saucedemo'da "Price (low to high)" tanlanganda narxlar `[7.99, 9.99, 15.99, 15.99, 29.99, 49.99]` chiqadi, standart tartibda esa `[29.99, 9.99, 15.99, 49.99, 7.99, 15.99]`. `isSorted(values, order)` funksiyasini yozing (`order`: `'asc'` yoki `'desc'`, standart `'asc'`). Teng qiymatlar (15.99, 15.99) xato hisoblanmasin.

<details>
<summary>💡 Javobni ko'rish</summary>

```typescript
export function isSorted(
  values: number[],
  order: "asc" | "desc" = "asc",
): boolean {
  return values.every((value, i) => {
    if (i === 0) return true;
    const previous = values[i - 1];
    return order === "asc" ? previous <= value : previous >= value;
  });
}

const lowToHigh = [7.99, 9.99, 15.99, 15.99, 29.99, 49.99];
const defaultOrder = [29.99, 9.99, 15.99, 49.99, 7.99, 15.99];

console.log(isSorted(lowToHigh)); // true
console.log(isSorted(defaultOrder)); // false
console.log(isSorted([...lowToHigh].reverse(), "desc")); // true
```

`<=` (qat'iy emas) — teng narxlar ham to'g'ri tartib. `[...lowToHigh].reverse()` — asl massivni buzmaslik uchun nusxa olamiz.

</details>

### Mashq 6: Asinxron API funksiya

`getBookingIds(firstname)` yozing: Restful-Booker'dan `GET /booking?firstname=...` bilan ID'lar ro'yxatini qaytarsin; status 2xx bo'lmasa xato tashlasin. Keyin `Promise.all` bilan ikki ism uchun parallel chaqiring va xatoni ushlang.

<details>
<summary>💡 Javobni ko'rish</summary>

```typescript
const BASE_URL = "https://restful-booker.herokuapp.com";

async function getBookingIds(firstname: string): Promise<number[]> {
  const url = `${BASE_URL}/booking?firstname=${encodeURIComponent(firstname)}`;
  const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
  if (!response.ok) {
    throw new Error(`GET /booking: status ${response.status}`);
  }
  const body = (await response.json()) as Array<{ bookingid: number }>;
  return body.map((item) => item.bookingid);
}

try {
  const [first, second] = await Promise.all([
    getBookingIds("Aziz"),
    getBookingIds("Jim"),
  ]);
  console.log("Aziz:", Array.isArray(first), "| Jim:", Array.isArray(second));
} catch (error) {
  console.error("Smoke yiqildi:", (error as Error).message);
  process.exitCode = 1;
}
```

`encodeURIComponent` — ismda probel yoki `'` bo'lsa ham URL buzilmaydi. ID'lar soni har safar boshqacha bo'lishi mumkin (servisdagi ma'lumotlar o'zgarib turadi), shuning uchun aniq son emas, **shakl** (massivligi) tekshiriladi.

</details>

### Mashq 7: Tartibni bashorat qiling

Quyidagi kod nima chiqaradi? Keyin uni tuzating.

```typescript
const sleep = (ms: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, ms));

async function login(): Promise<void> {
  await sleep(100);
  console.log("A: login tugadi");
}

async function runTest(): Promise<void> {
  console.log("B: test boshlandi");
  login();
  console.log("C: savatchani tekshirish");
  await sleep(200);
  console.log("D: test tugadi");
}

await runTest();
```

<details>
<summary>💡 Javobni ko'rish</summary>

```text
B: test boshlandi
C: savatchani tekshirish
A: login tugadi
D: test tugadi
```

Savatcha login tugashidan **oldin** tekshirildi. Tuzatish — `await login();`:

```text
B: test boshlandi
A: login tugadi
C: savatchani tekshirish
D: test tugadi
```

Bu kodda `await sleep(200)` tasodifan "yordam berib" turibdi: login 100 ms da tugaydi. Agar login 300 ms olsa, `A` umuman `D`dan keyin chiqardi. Mana shuning uchun unutilgan `await` testlarni "ba'zan o'tadigan" qiladi.

</details>

### Mashq 8: SMS kod qo'riqchisi (class + vaqt)

DemoBozor qoidasi: **3 marta noto'g'ri kod → 15 daqiqa blok**. `OtpGuard` class'ini yozing: `verify(code)` metodi `'OK' | 'WRONG' | 'LOCKED'` qaytarsin. Testda 15 daqiqa kutmaslik uchun vaqtni konstruktor orqali bering.

<details>
<summary>💡 Javobni ko'rish</summary>

```typescript
type OtpResult = "OK" | "WRONG" | "LOCKED";

export class OtpGuard {
  static readonly MAX_ATTEMPTS = 3;
  static readonly LOCK_MS = 15 * 60 * 1000;

  private failedAttempts = 0;
  private lockedUntil = 0;

  // vaqtni tashqaridan beramiz — testda "soatni" boshqarish oson bo'ladi
  constructor(
    private readonly correctCode: string,
    private readonly now: () => number = Date.now,
  ) {}

  verify(code: string): OtpResult {
    if (this.now() < this.lockedUntil) {
      return "LOCKED";
    }
    if (code === this.correctCode) {
      this.failedAttempts = 0;
      return "OK";
    }
    this.failedAttempts += 1;
    if (this.failedAttempts >= OtpGuard.MAX_ATTEMPTS) {
      this.lockedUntil = this.now() + OtpGuard.LOCK_MS;
      this.failedAttempts = 0;
      return "LOCKED";
    }
    return "WRONG";
  }
}

// "soxta soat" bilan tekshiramiz
let fakeNow = Date.parse("2026-10-09T10:00:00Z");
const guard = new OtpGuard("123456", () => fakeNow);

console.log(guard.verify("000000")); // WRONG
console.log(guard.verify("111111")); // WRONG
console.log(guard.verify("222222")); // LOCKED (3-xato)
console.log(guard.verify("123456")); // LOCKED (to'g'ri kod ham o'tmaydi)

fakeNow += 15 * 60 * 1000 - 1; // 14:59.999
console.log(guard.verify("123456")); // LOCKED
fakeNow += 1; // roppa-rosa 15 daqiqa
console.log(guard.verify("123456")); // OK
```

Bu yerda ikki muhim QA g'oyasi bor: (1) **chegara qiymati vaqtda ham ishlaydi** — 14:59.999 va 15:00.000; (2) **testlanuvchanlik** — vaqtni tashqaridan berish orqali 15 daqiqalik qoidani millisekundlarda tekshiramiz. Haqiqiy loyihada dasturchilardan aynan shunday "boshqariladigan soat" so'rang.

</details>

---

## 16. O'zingizni tekshiring {#checklist}

- [ ] Node 24 + TypeScript + tsx loyihasini noldan sozlay olaman
- [ ] `const`/`let`, union va literal tiplar, `unknown` va `any` farqini bilaman
- [ ] `interface` bilan test ma'lumoti modelini yoza olaman
- [ ] `map`, `filter`, `find`, `some`, `every`, `reduce` bilan ishlay olaman
- [ ] `??` va `||` farqini va truthy/falsy tuzog'ini tushuntira olaman
- [ ] Class, `private`, `readonly`, getter'lardan foydalana olaman
- [ ] `async/await`, `Promise.all`, `try/catch/finally`ni bilaman va unutilgan `await` xavfini tushunaman
- [ ] `fetch` bilan API'ga so'rov yuborib, statusni tekshira olaman
- [ ] `.env` va faker bilan test ma'lumotlarini boshqara olaman

---

## Xulosa {#xulosa}

- TypeScript = JavaScript + tiplar: xatolar test ishga tushishidan oldin ko'rinadi. Playwright TS'ni o'zi ishga tushiradi, tiplarni esa `tsc --noEmit` tekshiradi.
- Muhit: Node 24 LTS, `typescript` (7.0.2), `tsx`, `@types/node@24`; `tsconfig.json`da `strict: true` va TypeScript 7 uchun `types: ["node"]`.
- Literal tiplar (`OrderStatus`) va `interface`lar test ma'lumotlarini xavfsiz qiladi; `never` bilan to'liqlik tekshiruvi yangi statuslarni unutishga yo'l qo'ymaydi.
- Massiv metodlari — UI va API natijalarini tekshirishning asosiy quroli; standart qiymat uchun `??`, `||` emas.
- Class'lar Page Object Model'ning poydevori: `private readonly`, getter, validatsiya va "boshqariladigan soat".
- Asinxron kodda `await`ni unutish — flaky testning klassik sababi; `no-floating-promises` lint qoidasini yoqing.
- `fetch` + `AbortSignal.timeout` + `response.ok` — API smoke'ning minimal to'plami; tashqi ma'lumotni `as` emas, validatsiya bilan tekshirish kerak (QA 20).
- **Keyingi dars:** [QA 18: Playwright (1) — o'rnatish, locator'lar, action'lar, assertion'lar va auto-waiting](/ios-lessons/qa-playwright-basics)

## Manbalar {#manbalar}

1. [TypeScript — rasmiy hujjatlar](https://www.typescriptlang.org/docs/)
2. [TypeScript Handbook — The Basics](https://www.typescriptlang.org/docs/handbook/2/basic-types.html)
3. [Microsoft DevBlogs — Announcing TypeScript 7.0](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/)
4. [Playwright — TypeScript](https://playwright.dev/docs/test-typescript)
5. [Playwright — Best Practices (lint va tsc)](https://playwright.dev/docs/best-practices)
6. [typescript-eslint — no-floating-promises](https://typescript-eslint.io/rules/no-floating-promises/)
7. [Node.js — Download](https://nodejs.org/en/download)
8. [Node.js — Globals (fetch va boshqalar)](https://nodejs.org/api/globals.html)
9. [MDN — Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
10. [MDN — async function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)
11. [MDN — Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
12. [tsx — GitHub](https://github.com/privatenumber/tsx)
13. [Faker — Guide](https://fakerjs.dev/guide/) va [Localization](https://fakerjs.dev/guide/localization)
14. [npm — npm ci](https://docs.npmjs.com/cli/v11/commands/npm-ci)
15. [Restful-Booker API hujjati](https://restful-booker.herokuapp.com/apidoc/index.html)
16. [Playwright Test for VS Code](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright)
