# Màn hình "Tra cứu hồ sơ, tài liệu" (Search)

> Tài liệu kỹ thuật cho màn hình `/search`.
> Dùng chung cho mọi UC thao tác trên màn này (UC59, …).
> Phần fixture/account/TIMEOUT/PW method dùng chung toàn dự án xem `tests/README.md`.

> 📌 **Phạm vi tài liệu — chỉ mô tả màn hình** (field, nút, testId, cấu trúc modal/bảng, thông báo,
> luồng thao tác). **Không** ghi logic test / kỳ vọng của case / quy tắc nghiệp vụ vào đây — chỗ của
> chúng là spec `tests/<mục>/`, `<mục>.steps.ts` và `<mục>.md`.

---

## 0. File function đi kèm — `TRA_CUU.function.ts`

**Các thao tác dùng chung của màn này đã được đóng gói thành hàm** trong `src/screen-instructions/TRA_CUU.function.ts`.
Khi viết spec: **import hàm từ file đó, KHÔNG copy code dài vào spec.**

```ts
import {
  gotoSearch,
  findExactRow,
  verifyPermissionIcon,
  verifySearchResults,
  KHONG_CO_QUYEN,
} from "../../../src/screen-instructions/TRA_CUU.function";
```

| Hàm | Mục tài liệu | Công dụng |
| --- | :---: | --- |
| `gotoSearch(page, pw, loaiBoHoSo)` | 2 | Goto `/search`, chờ màn load, lọc theo "Loại hồ sơ" |
| `findExactRow(page, exactName)` → `Locator \| null` | 4 | Tìm đúng `<tr>` khớp CHÍNH XÁC tên (tránh nhầm dòng khi tên có tiền tố trùng) |
| `verifyPermissionIcon(page, pw, keyword, expected, label)` | 5 | Search theo keyword rồi assert tooltip cột "Quyền" (`expected: { denied, tooltipContains? }`) |
| `verifySearchResults(page, keyword)` | 4 | Assert mọi dòng kết quả có `<mark>` highlight ở cell Mã hoặc Tên |
| `KHONG_CO_QUYEN` | 5 | Hằng text tooltip khi không có quyền: `"Người dùng không có quyền truy cập"` |

> 📌 **Quy tắc bổ sung hàm mới**: khi phát hiện thao tác dài (> ~10 dòng) cần dùng ở ≥ 2 spec,
> **viết thành hàm vào `TRA_CUU.function.ts`** rồi cập nhật bảng trên — không copy code dài vào từng spec.
> Các đoạn code chi tiết bên dưới (mục 2–5) là **tài liệu giải thích cơ chế bên trong hàm** — chỉ dùng khi cần viết thao tác biến thể.

Các hàm dùng chung từ màn "Kho tài liệu" thường được import để **tạo dữ liệu nguồn** trước khi test search
(hồ sơ/tài liệu phải tồn tại và ở trạng thái "Hoạt động" thì mới tìm thấy ở màn tra cứu):

```ts
import {
  createHoSo,
  addDocument,
  chuyenHoatDong,
  DOC_PERM,
} from "../../../src/screen-instructions/KHO_TAI_LIEU.function";
```

---

## 1. Thông tin màn hình

| Mục          | Giá trị                                          |
| ------------ | ------------------------------------------------- |
| URL          | `/search`                                          |
| Hằng URL     | `uc59DefaultUrl` (export từ `uc59.default.data`)   |

```ts
// tests/uc59/uc59.default.data.ts
export const uc59DefaultUrl = "/search";
```

---

## 2. Truy cập màn hình & bộ lọc "Loại hồ sơ"

> ✅ Dùng hàm **`gotoSearch(page, pw, loaiBoHoSo)`** (`TRA_CUU.function.ts`) — không tự lặp lại đoạn dưới trong spec mới.

```ts
// Cơ chế bên trong gotoSearch:
await page.goto(`${BASE_URL}/search`);
await pw.isVisible("txt-recent-search-keyword", TIMEOUT.PAGE_LOADING);
await pw.wait(TIMEOUT.HARD_WAITING);
await pw.inputDropDownListWithSearch("sel-loai-bo-ho-so", loaiBoHoSo, "exact");
```

> ⚠️ Dùng `matchMode: "exact"` cho `sel-loai-bo-ho-so` — tránh khớp nhầm option có tên chứa chuỗi con.
> `loaiBoHoSo` thường lấy lại từ `createHoSo(..., { readFields: ["sel-loaiBoHoSo"] })` (xem `KHO_TAI_LIEU.md` mục 5)
> để đảm bảo filter đúng loại của hồ sơ vừa tạo.

---

## 3. Button / element của màn hình

| testId / locator                 | Mô tả                                                              |
| --------------------------------- | ------------------------------------------------------------------- |
| `txt-recent-search-keyword`       | Ô nhập từ khóa tìm kiếm — cũng là mốc chờ màn load xong             |
| `btn-save-search`                 | Nút **Tìm kiếm**                                                    |
| `sel-loai-bo-ho-so`               | Dropdown lọc "Loại hồ sơ" (dùng `inputDropDownListWithSearch`, `matchMode="exact"`) |
| `lbl-ma-ho-so-<id>` (prefix)      | Cell "Mã hồ sơ/tài liệu" trong bảng kết quả — mỗi dòng 1 testId khác nhau, phải query bằng `[data-testid^="lbl-ma-ho-so-"]` |
| `lbl-ten-ho-so-<id>` (prefix)     | Cell "Tên hồ sơ/tài liệu" trong bảng kết quả — query bằng `[data-testid^="lbl-ten-ho-so-"]` |

### Cấu trúc cột bảng kết quả (theo `td` index)

| `td` index | Nội dung                     |
| ---------- | ----------------------------- |
| 0          | Checkbox                      |
| 1          | STT                            |
| 2          | Icon loại (hồ sơ/tài liệu)     |
| 3          | Mã (`lbl-ma-ho-so-*`)          |
| 4          | Quyền (icon + tooltip, xem mục 5) |
| 5          | Tên (`lbl-ten-ho-so-*`)        |

---

## 4. Tìm kiếm theo từ khóa & kiểm tra highlight

```ts
await pw.inputText("txt-recent-search-keyword", keyword);
await pw.clickButton("btn-save-search");
await pw.wait(TIMEOUT.DATA_LOADING);
```

Kết quả trả về highlight từ khóa khớp bằng thẻ `<mark>` trong cell Mã hoặc Tên. Mọi dòng đang hiển thị
phải có **ít nhất 1** trong 2 cell (Mã hoặc Tên) chứa `<mark>` — nếu không, nghi ngờ hệ thống đang search
theo chỉ mục nội dung tài liệu thay vì Mã/Tên hồ sơ, tài liệu.

> ✅ Dùng hàm **`verifySearchResults(page, keyword)`** (`TRA_CUU.function.ts`) sau khi search:

```ts
await pw.inputText("txt-recent-search-keyword", keyword);
await pw.clickButton("btn-save-search");
await pw.wait(TIMEOUT.DATA_LOADING);
await verifySearchResults(page, keyword);
```

> 📌 Test từ khóa nên phủ ít nhất 3 case: từ khóa đầy đủ (vd marker timestamp), một vài ký tự cuối
> (từ khóa ngắn/rút gọn), và 1 ký tự đơn lẻ (vd `"E"`) — xem UC59 1.1.10.

### Tìm đúng dòng khớp CHÍNH XÁC tên

Tên tài liệu thường có tên hồ sơ làm tiền tố (vd `${hoSoName}-TL-A`) → search theo `hoSoName` trả về
nhiều dòng (cả BHS lẫn TL). Không dùng `.first()` — phải so khớp full text.

> ✅ Dùng hàm **`findExactRow(page, exactName)`** (`TRA_CUU.function.ts`) → trả về `Locator` của `<tr>` hoặc `null` nếu không tìm thấy:

```ts
const row = await findExactRow(page, exactName);
```

---

## 5. Cột "Quyền" — icon + tooltip phân quyền

Cột "Quyền" (`td` index 4) phản ánh đúng `permissionACECurrentUser` của user hiện tại trên item đó
(logic `getPermissionIcon` phía FE — `SearchResult.tsx`), **không phụ thuộc** hành vi click (click có thể
bị bypass bởi `checkColor` khi user cùng Kho lưu trữ với item — không đáng tin cậy để test phân quyền).

- Nếu user **privileged** (Admin / Thủ thư / cùng Kho lưu trữ) → cột **rỗng, không có icon** → luôn coi là có quyền.
- Nếu **có icon** → hover để hiện tooltip `.ant-tooltip-inner`:
  - Không có quyền → tooltip = `KHONG_CO_QUYEN` (`"Người dùng không có quyền truy cập"`)
  - Có quyền → tooltip chứa cụm mô tả quyền cụ thể (vd `"owner"`, `"Xem"`, …) tuỳ ACE trên item

> ✅ Dùng hàm **`verifyPermissionIcon(page, pw, keyword, expected, label)`** (`TRA_CUU.function.ts`) — tự search
> theo keyword, tìm đúng dòng qua `findExactRow`, rồi assert cột Quyền (`td` index 4):

```ts
// expected.denied = true              -> tooltip phải là KHONG_CO_QUYEN
// expected.tooltipContains = "Xem"    -> tooltip phải chứa đúng cụm này (biết trước quyền cụ thể)
// không truyền tooltipContains        -> chỉ assert KHÔNG bị chặn quyền (quyền kế thừa/cascade)
await verifyPermissionIcon(page, pw, keyword, { denied: false, tooltipContains: "Xem" }, "label");
```

Cơ chế bên trong (khi cần viết biến thể):

```ts
const permCell = row.locator("td").nth(4);
const iconCount = await permCell.locator("svg, span").count();

if (iconCount === 0) {
  // Không hiện icon -> privileged -> luôn có quyền
} else {
  await permCell.hover();
  const tooltip = page.locator(".ant-tooltip-inner");
  // denied -> toContainText(KHONG_CO_QUYEN)
  // biết trước quyền cụ thể -> toContainText(expected)
  // không biết trước (quyền kế thừa/cascade) -> not.toContainText(KHONG_CO_QUYEN)
}
```

### Quy tắc phân quyền quan sát được qua UC59 1.1.11

| Trường hợp                                                  | Kết quả                                                          |
| -------------------------------------------------------------- | -------------------------------------------------------------------- |
| Owner BHS                                                        | Có quyền trên BHS (tooltip chứa `"owner"`) và **mọi** tài liệu bên trong |
| Chỉ có quyền riêng (vd VIEW) trên 1 tài liệu, không có quyền trên BHS | Có quyền trên tài liệu đó (tooltip chứa quyền tương ứng, vd `"Xem"`) **và tự động có quyền trên BHS chứa nó** — nhưng **không** lan sang tài liệu khác cùng BHS |
| Có quyền (vd VIEW) trên BHS, tài liệu không có phân quyền riêng     | Kế thừa quyền BHS → có quyền trên tài liệu đó                        |
| Có quyền trên BHS, nhưng tài liệu có phân quyền riêng không gồm user | Không có quyền trên tài liệu đó (phân quyền riêng override kế thừa)  |
| Thủ thư kho / Admin                                              | Luôn có quyền trên mọi BHS/TL (privileged — cột Quyền thường rỗng)   |

---

## 6. Lưu ý quan trọng

1. `pw.isVisible("txt-recent-search-keyword", TIMEOUT.PAGE_LOADING)` rồi `pw.wait(TIMEOUT.HARD_WAITING)` trước khi thao tác — dùng làm mốc chờ màn `/search` load xong.
2. Hồ sơ phải được **chuyển "Hoạt động"** (`chuyenHoatDong` — xem `KHO_TAI_LIEU.md` mục 5) thì end_user mới tìm thấy ở màn tra cứu; hồ sơ "Khai báo" chỉ Admin/Thủ thư thấy được.
3. Khi test song song nhiều case dùng chung 1 bộ dữ liệu (nhiều account cùng tra cứu 1 hồ sơ) → tạo dữ liệu **1 lần** bằng file lock + cache JSON (xem pattern `getOrCreateSharedData`/`DATA_FILE`/`LOCK_FILE` trong UC59 1.1.4-9), tránh mỗi test/retry tạo lại dữ liệu trùng.
4. Nếu 2 test cùng cần `beforeAll` tạo dữ liệu chung và chạy `fullyParallel: true` → thêm `test.describe.configure({ mode: "serial" })` để tránh `beforeAll` (scope theo worker) chạy nhiều lần và tạo trùng dữ liệu.
5. Query cell theo prefix testId (`[data-testid^="lbl-ma-ho-so-"]`, `[data-testid^="lbl-ten-ho-so-"]`) vì mỗi dòng có testId khác nhau (gắn theo id item) — không có testId cố định cho toàn cột.
6. Sau khi tìm được dòng qua `findExactRow`, luôn **so khớp lại** text của cell Tên với keyword mong đợi trước khi assert tiếp — tránh trỏ nhầm dòng khi có nhiều item tên trùng tiền tố (đã tự làm bên trong `verifyPermissionIcon`).
7. **Thao tác dài mới → bổ sung vào `TRA_CUU.function.ts`** (xem mục 0) — không copy code dài vào spec.
