import { describe, it, expect, beforeEach, vi } from "vitest"; import { screen, fireEvent } from "@testing-library/react"; import { renderWithProviders } from "../test/utils"; vi.mock("sonner", () => ({ toast: { success: vi.fn(), error: vi.fn() } })); vi.mock("../lib/api", () => ({ api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() }, ApiError: class extends Error {}, })); vi.mock("../stores/authStore", () => ({ useAuthStore: () => ({ doctorUuid: "doc-1", dbUuid: "doc-1", primaryRole: "doctor" }), })); vi.mock("../hooks/useSubscription", () => ({ useSubscription: () => ({ maxSecretaries: 5 }), })); import { api } from "../lib/api"; import MySecretariesPage from "./MySecretariesPage"; const get = api.get as ReturnType; const fullPerms = { appointments: { view: true, create: false, cancel: false, update_status: false }, patients: { view: false, create: false, update: false, delete: false }, payments: { view: false, create: false, update: false, delete: false }, insurances: { view: false, create: false, update: false, delete: false }, addresses: { view: false, create: false, update: false, delete: false }, clinic_info: { view: false, update: false }, }; const activeSecretary = { uuid: "sec-1", user_name: "سارا احمدی", mobile_number: "09121234567", doctor_name: "دکتر تست", doctor_uuid: "doc-1", is_active: true, national_code: "1234567890", address: "یزد", permissions: fullPerms, created_at: 1700000000, }; const previousSecretary = { ...activeSecretary, uuid: "sec-2", user_name: "مینا رضایی", mobile_number: "09129876543", is_active: false, national_code: "9999999999", }; /** فهرست بخش‌ها از GET /api/v1/permission-catalog می‌آید، نه از کد کامپوننت. */ const CATALOG = [ { key: "appointments", label: "مدیریت نوبت‌ها", clinic_only: false, actions: [{ key: "view", label: "مشاهده نوبت‌ها" }, { key: "create", label: "ایجاد نوبت" }], }, { key: "patients", label: "پرونده بیماران", clinic_only: false, actions: [{ key: "view", label: "مشاهده بیماران" }], }, { key: "clinic_doctors", label: "مدیریت پزشکان کلینیک", clinic_only: true, actions: [{ key: "view", label: "مشاهده پزشکان" }], }, ]; function mockData(rows = [activeSecretary, previousSecretary], catalog: unknown[] = CATALOG) { get.mockImplementation((url: string) => { if (url.includes("permission-catalog")) { return Promise.resolve({ success: true, data: { version: 1, resources: catalog } }); } if (url.includes("/secretaries/")) return Promise.resolve({ success: true, data: rows }); return Promise.resolve({ success: true, data: [] }); }); } beforeEach(() => { get.mockReset(); mockData(); }); describe("MySecretariesPage", () => { it("renders the title and both tabs", async () => { renderWithProviders(, { route: "/admin/my-secretaries" }); expect(await screen.findByText("لیست منشی ها")).toBeInTheDocument(); expect(screen.getByText("منشی های فعلی")).toBeInTheDocument(); expect(screen.getByText("منشی های قبلی")).toBeInTheDocument(); }); it("shows active secretaries with national code on the default tab", async () => { renderWithProviders(, { route: "/admin/my-secretaries" }); expect(await screen.findAllByText("سارا احمدی")).not.toHaveLength(0); expect(screen.getAllByText("1234567890").length).toBeGreaterThan(0); // inactive secretary is hidden on the active tab expect(screen.queryByText("مینا رضایی")).not.toBeInTheDocument(); }); it("switches to the previous tab and lists inactive secretaries", async () => { renderWithProviders(, { route: "/admin/my-secretaries" }); await screen.findAllByText("سارا احمدی"); fireEvent.click(screen.getByText("منشی های قبلی")); expect(await screen.findAllByText("مینا رضایی")).not.toHaveLength(0); expect(screen.queryByText("سارا احمدی")).not.toBeInTheDocument(); }); it("shows an empty state when there are no active secretaries", async () => { mockData([previousSecretary]); renderWithProviders(, { route: "/admin/my-secretaries" }); expect(await screen.findByText("هنوز منشی فعالی اضافه نشده است")).toBeInTheDocument(); }); // ── جداسازی فرمِ منشی از دسترسی‌ها ────────────────────────────────────── // مودالِ افزودن دیگر مجوز ندارد؛ مجوزها مودالِ خودشان را دارند تا افزودنِ یک // منشی به تصمیم‌گیری دربارهٔ همهٔ منابع گره نخورد. it("keeps permissions out of the add form", async () => { renderWithProviders(, { route: "/admin/my-secretaries" }); await screen.findAllByText("سارا احمدی"); fireEvent.click(screen.getByText("اضافه کردن منشی")); expect(await screen.findByText("اضافه کردن منشی جدید")).toBeInTheDocument(); expect(screen.queryByText("مدیریت نوبت‌ها")).not.toBeInTheDocument(); expect(screen.queryByText("پرونده بیماران")).not.toBeInTheDocument(); }); it("opens a separate permissions modal from the row action", async () => { renderWithProviders(, { route: "/admin/my-secretaries" }); await screen.findAllByText("سارا احمدی"); fireEvent.click(screen.getAllByRole("button", { name: "دسترسی‌ها" })[0]); expect(await screen.findByText("دسترسی‌های سارا احمدی")).toBeInTheDocument(); expect(await screen.findByText("مدیریت نوبت‌ها")).toBeInTheDocument(); expect(screen.getByText("پرونده بیماران")).toBeInTheDocument(); }); /** * همان تضمینِ «داینامیک بودن»: منبع تازه فقط به mock اضافه می‌شود و هیچ خطی از * کد کامپوننت عوض نمی‌شود. */ it("shows a resource added to the catalog with no code change", async () => { mockData(undefined, [ ...CATALOG, { key: "brand_new_page", label: "صفحهٔ کاملاً تازه", clinic_only: false, actions: [{ key: "view", label: "مشاهده" }] }, ]); renderWithProviders(, { route: "/admin/my-secretaries" }); await screen.findAllByText("سارا احمدی"); fireEvent.click(screen.getAllByRole("button", { name: "دسترسی‌ها" })[0]); expect(await screen.findByText("صفحهٔ کاملاً تازه")).toBeInTheDocument(); }); /** منبعِ clinic_only برای پزشکِ مستقل (primaryRole=doctor) نباید دیده شود. */ it("hides clinic-only resources for a standalone doctor", async () => { renderWithProviders(, { route: "/admin/my-secretaries" }); await screen.findAllByText("سارا احمدی"); fireEvent.click(screen.getAllByRole("button", { name: "دسترسی‌ها" })[0]); expect(await screen.findByText("مدیریت نوبت‌ها")).toBeInTheDocument(); expect(screen.queryByText("مدیریت پزشکان کلینیک")).not.toBeInTheDocument(); }); /** آکاردئون‌ها بسته باز می‌شوند و شمارِ روشن‌ها روی هدر دیده می‌شود. */ it("starts every section collapsed with a granted counter", async () => { renderWithProviders(, { route: "/admin/my-secretaries" }); await screen.findAllByText("سارا احمدی"); fireEvent.click(screen.getAllByRole("button", { name: "دسترسی‌ها" })[0]); await screen.findByText("مدیریت نوبت‌ها"); // appointments.view=true در fullPerms → «۱ از ۲» با ارقام فارسی expect(screen.getByText("۱ از ۲")).toBeInTheDocument(); // بسته است، پس سوییچِ داخلش رندر نشده expect(screen.queryByLabelText("مدیریت نوبت‌ها — مشاهده نوبت‌ها")).not.toBeInTheDocument(); fireEvent.click(screen.getByText("مدیریت نوبت‌ها")); expect(await screen.findByLabelText("مدیریت نوبت‌ها — مشاهده نوبت‌ها")).toBeInTheDocument(); }); });