/** * @vitest-environment jsdom */ import { cleanup, render, screen, waitFor } from "@testing-library/react"; import { getPromoRuleList } from "@workspace/ui/services/admin/promo"; import type { AxiosResponse } from "axios"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import PromoPage from "."; vi.mock("react-i18next", () => ({ useTranslation: () => ({ t: (_key: string, fallback: string): string => fallback, }), })); vi.mock("@/stores/subscribe", () => ({ useSubscribe: () => ({ subscribes: [ { id: 8, name: "Monthly Plan", unit_price: 1200, sold: 0, }, ], getSubscribeName: (id?: number) => (id === 8 ? "Monthly Plan" : "--"), }), })); vi.mock("@/utils/common", () => ({ formatDate: (timestamp: number) => `date-${timestamp}`, })); vi.mock("@workspace/ui/services/admin/promo", () => ({ createPromoPrice: vi.fn(), createPromoRule: vi.fn(), deletePromoPrice: vi.fn(), deletePromoRule: vi.fn(), getPromoPriceList: vi.fn(), getPromoRuleList: vi.fn(), getPromoUsageList: vi.fn(), updatePromoRule: vi.fn(), })); const mockedGetPromoRuleList = vi.mocked(getPromoRuleList); function createRuleListResponse( data: API.GetPromoRuleListResponse ): AxiosResponse { return { data: { code: 200, data, }, status: 200, statusText: "OK", headers: {}, config: { headers: {} as AxiosResponse["config"]["headers"], }, }; } beforeEach(() => { localStorage.setItem("timezone", "UTC"); }); afterEach(() => { cleanup(); vi.clearAllMocks(); }); describe("PromoPage", () => { it("renders promo rules with type, priority, status, and validity period", async () => { mockedGetPromoRuleList.mockResolvedValue( createRuleListResponse({ total: 1, list: [ { id: 1, name: "Return User Price", type: "inactive_user", params: { inactive_months: 3 }, priority: 5, enabled: true, start_time: 1_716_800_000, end_time: 1_716_900_000, }, ], }) ); render(); await waitFor(() => expect(mockedGetPromoRuleList).toHaveBeenCalledWith({ page: 1, size: 200, search: undefined, type: undefined, enabled: undefined, }) ); expect(await screen.findByText("Return User Price")).not.toBeNull(); expect(screen.getByText("inactive_user")).not.toBeNull(); expect(screen.getByText("5")).not.toBeNull(); expect(screen.getByText("date-1716800000")).not.toBeNull(); expect(screen.getByText("date-1716900000")).not.toBeNull(); }); it("shows the empty state when there are no promo rules", async () => { mockedGetPromoRuleList.mockResolvedValue( createRuleListResponse({ total: 0, list: [], }) ); render(); expect(await screen.findByText("No promo rules")).not.toBeNull(); }); it("shows a loading state while promo rules are being fetched", () => { let resolveRequest: | (( value: AxiosResponse< API.Response & { data?: API.GetPromoRuleListResponse } > ) => void) | undefined; mockedGetPromoRuleList.mockReturnValue( new Promise((resolve) => { resolveRequest = resolve; }) ); render(); expect(screen.getByRole("status", { name: "Loading data" })).not.toBeNull(); expect(resolveRequest).toBeTypeOf("function"); }); it("shows an error state when promo rules fail to load", async () => { mockedGetPromoRuleList.mockRejectedValue(new Error("network failed")); render(); await waitFor(() => expect(mockedGetPromoRuleList).toHaveBeenCalled()); expect( await screen.findByText("Failed to load promo rules") ).not.toBeNull(); expect(screen.queryByText("No promo rules")).toBeNull(); }); });