- Redesign MySecretariesPage pixel-perfect to clinic-pro-tauri (active/previous tabs, desktop table, mobile cards, add/edit/view modal with permission accordions, deactivate confirm) - Permission sections based on existing admin pages (appointments, patients, payments, insurances, addresses, clinic_info) - Extend DoctorSecretary with national_code + address columns (+migration); wire create/update in SecretaryController; add patients/payments to DEFAULT_PERMISSIONS - Extend Secretary/SecretaryPermissions types; update admin SecretariesPage - Backend + frontend tests; update docs/api/secretary.md Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
111 lines
4.6 KiB
TypeScript
111 lines
4.6 KiB
TypeScript
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<typeof vi.fn>;
|
|
|
|
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",
|
|
};
|
|
|
|
function mockData(rows = [activeSecretary, previousSecretary]) {
|
|
get.mockImplementation((url: string) => {
|
|
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(<MySecretariesPage />, { 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(<MySecretariesPage />, { 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(<MySecretariesPage />, { 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(<MySecretariesPage />, { route: "/admin/my-secretaries" });
|
|
expect(await screen.findByText("هنوز منشی فعالی اضافه نشده است")).toBeInTheDocument();
|
|
});
|
|
|
|
it("opens the add modal with permission sections based on existing pages", async () => {
|
|
renderWithProviders(<MySecretariesPage />, { route: "/admin/my-secretaries" });
|
|
await screen.findAllByText("سارا احمدی");
|
|
|
|
fireEvent.click(screen.getByText("اضافه کردن منشی"));
|
|
|
|
expect(await screen.findByText("اضافه کردن منشی جدید")).toBeInTheDocument();
|
|
expect(screen.getByText("مجوزهای دسترسی")).toBeInTheDocument();
|
|
expect(screen.getByText("مدیریت نوبتها")).toBeInTheDocument();
|
|
expect(screen.getByText("پرونده بیماران")).toBeInTheDocument();
|
|
expect(screen.getByText("مدیریت پرداختها")).toBeInTheDocument();
|
|
expect(screen.getByText("مدیریت بیمهها")).toBeInTheDocument();
|
|
});
|
|
});
|