import { act, renderHook } from "@testing-library/react"; import { afterEach, beforeEach, expect, test, vi } from "vitest"; import { useResourceListPage } from "./useResourcePages.js"; const mocks = vi.hoisted(() => ({ batchDeleteResources: vi.fn(), can: vi.fn(), confirm: vi.fn(), deleteResource: vi.fn(), reload: vi.fn(), showToast: vi.fn(), usePaginatedQuery: vi.fn(), })); vi.mock("@/app/auth/AuthProvider.jsx", () => ({ useAuth: () => ({ can: mocks.can }), })); vi.mock("@/shared/hooks/usePaginatedQuery.js", () => ({ usePaginatedQuery: mocks.usePaginatedQuery, })); vi.mock("@/shared/ui/ConfirmProvider.jsx", () => ({ useConfirm: () => mocks.confirm, })); vi.mock("@/shared/ui/ToastProvider.jsx", () => ({ useToast: () => ({ showToast: mocks.showToast }), })); vi.mock("@/features/resources/api", async (importOriginal) => ({ ...(await importOriginal()), batchDeleteResources: mocks.batchDeleteResources, deleteResource: mocks.deleteResource, })); beforeEach(() => { mocks.can.mockReturnValue(true); mocks.confirm.mockResolvedValue(true); mocks.batchDeleteResources.mockResolvedValue([]); mocks.deleteResource.mockResolvedValue({}); mocks.reload.mockResolvedValue(undefined); mocks.usePaginatedQuery.mockReturnValue({ data: resourcePage(), error: null, loading: false, reload: mocks.reload, }); }); afterEach(() => { vi.clearAllMocks(); }); test("deleting a resource hides only that row without reloading the list", async () => { const { result } = renderHook(() => useResourceListPage()); let deletedResourceIds = []; await act(async () => { deletedResourceIds = await result.current.deleteResources([resourceFixture({ resourceId: 11 })]); }); expect(deletedResourceIds).toEqual([11]); expect(mocks.deleteResource).toHaveBeenCalledWith(11); expect(mocks.reload).not.toHaveBeenCalled(); expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([12]); expect(mocks.showToast).toHaveBeenCalledWith("资源已删除", "success"); }); test("batch resource delete hides successful rows without reloading the list", async () => { const { result } = renderHook(() => useResourceListPage()); let deletedResourceIds = []; await act(async () => { deletedResourceIds = await result.current.deleteResources([ resourceFixture({ resourceId: 11 }), resourceFixture({ resourceId: 12 }), ]); }); expect(deletedResourceIds).toEqual([11, 12]); expect(mocks.batchDeleteResources).toHaveBeenCalledWith([11, 12]); expect(mocks.deleteResource).not.toHaveBeenCalled(); expect(mocks.reload).not.toHaveBeenCalled(); expect(result.current.data.items).toEqual([]); expect(mocks.showToast).toHaveBeenCalledWith("已删除 2 个资源", "success"); }); test("failed batch resource delete keeps all rows visible", async () => { mocks.usePaginatedQuery.mockReturnValue({ data: { items: [ resourceFixture({ resourceId: 11 }), resourceFixture({ resourceId: 12 }), resourceFixture({ resourceId: 13 }), ], page: 1, pageSize: 50, total: 3, }, error: null, loading: false, reload: mocks.reload, }); mocks.batchDeleteResources.mockRejectedValueOnce(new Error("delete failed")); const { result } = renderHook(() => useResourceListPage()); let deletedResourceIds = []; await act(async () => { deletedResourceIds = await result.current.deleteResources([ resourceFixture({ resourceId: 11 }), resourceFixture({ resourceId: 12 }), resourceFixture({ resourceId: 13 }), ]); }); expect(deletedResourceIds).toEqual([]); expect(mocks.batchDeleteResources).toHaveBeenCalledWith([11, 12, 13]); expect(mocks.deleteResource).not.toHaveBeenCalled(); expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([11, 12, 13]); expect(mocks.showToast).toHaveBeenCalledWith("delete failed", "error"); }); test("failed resource delete keeps the row visible", async () => { mocks.deleteResource.mockRejectedValueOnce(new Error("delete failed")); const { result } = renderHook(() => useResourceListPage()); let deletedResourceIds = []; await act(async () => { deletedResourceIds = await result.current.deleteResources([resourceFixture({ resourceId: 11 })]); }); expect(deletedResourceIds).toEqual([]); expect(mocks.reload).not.toHaveBeenCalled(); expect(result.current.data.items.map((resource) => resource.resourceId)).toEqual([11, 12]); expect(mocks.showToast).toHaveBeenCalledWith("delete failed", "error"); }); function resourcePage() { return { items: [resourceFixture({ resourceId: 11, name: "Rose" }), resourceFixture({ resourceId: 12, name: "Ring" })], page: 1, pageSize: 50, total: 2, }; } function resourceFixture(patch = {}) { return { coinPrice: 10, name: "Rose", resourceCode: "gift_rose", resourceId: 11, resourceType: "gift", status: "active", ...patch, }; }