import { act, renderHook } from "@testing-library/react"; import { afterEach, beforeEach, expect, test, vi } from "vitest"; import { setSelectedAppCode } from "@/shared/api/request"; const mocks = vi.hoisted(() => ({ appCode: "lalu", confirm: vi.fn(), createH5Link: vi.fn(), deleteH5Link: vi.fn(), listH5Links: vi.fn(), queryFn: null, queryOptions: null, reload: vi.fn(), showToast: vi.fn(), updateH5Link: vi.fn() })); vi.mock("@/app/app-scope/AppScopeProvider.jsx", () => ({ useAppScope: () => ({ appCode: mocks.appCode }) })); vi.mock("@/features/app-config/api", () => ({ createH5Link: mocks.createH5Link, deleteH5Link: mocks.deleteH5Link, listH5Links: mocks.listH5Links, updateH5Link: mocks.updateH5Link })); vi.mock("@/features/app-config/permissions.js", () => ({ useAppConfigAbilities: () => ({ canUpdate: true, canView: true }) })); vi.mock("@/shared/hooks/useAdminQuery.js", () => ({ useAdminQuery: (queryFn, options) => { mocks.queryFn = queryFn; mocks.queryOptions = options; return { data: { items: [], total: 0 }, error: "", loading: false, reload: mocks.reload }; } })); vi.mock("@/shared/ui/ConfirmProvider.jsx", () => ({ useConfirm: () => mocks.confirm })); vi.mock("@/shared/ui/ToastProvider.jsx", () => ({ useToast: () => ({ showToast: mocks.showToast }) })); import { useH5ConfigPage } from "./useH5ConfigPage.js"; beforeEach(() => { mocks.appCode = "lalu"; mocks.confirm.mockResolvedValue(true); mocks.createH5Link.mockResolvedValue({}); mocks.deleteH5Link.mockResolvedValue({ deleted: true }); mocks.listH5Links.mockResolvedValue({ items: [], total: 0 }); mocks.reload.mockResolvedValue({ items: [], total: 0 }); mocks.updateH5Link.mockResolvedValue({}); setSelectedAppCode("lalu"); }); afterEach(() => { vi.clearAllMocks(); setSelectedAppCode("lalu"); }); test("uses appCode in the H5 query key and read request", async () => { const { rerender } = renderHook(() => useH5ConfigPage()); expect(mocks.queryOptions.queryKey).toEqual(["app-config", "h5-links", "lalu"]); await mocks.queryFn(); expect(mocks.listH5Links).toHaveBeenLastCalledWith("lalu"); mocks.appCode = "huwaa"; setSelectedAppCode("huwaa"); rerender(); expect(mocks.queryOptions.queryKey).toEqual(["app-config", "h5-links", "huwaa"]); await mocks.queryFn(); expect(mocks.listH5Links).toHaveBeenLastCalledWith("huwaa"); }); test("closes an edit dialog when the selected App changes", () => { const item = { appCode: "lalu", key: "achievement", label: "成就", updatedAtMs: 1000, url: "https://h5.example.com/achievement" }; const { result, rerender } = renderHook(() => useH5ConfigPage()); act(() => result.current.openEdit(item)); expect(result.current.activeAction).toBe("edit"); mocks.appCode = "huwaa"; setSelectedAppCode("huwaa"); rerender(); expect(result.current.activeAction).toBe(""); expect(result.current.editingItem).toBeNull(); expect(result.current.form).toEqual({ key: "", label: "", url: "" }); }); test("does not submit stale form state after an App switch", async () => { const { result, rerender } = renderHook(() => useH5ConfigPage()); act(() => { result.current.openEdit({ appCode: "lalu", key: "achievement", label: "成就", updatedAtMs: 1000, url: "https://h5.example.com/achievement" }); }); mocks.appCode = "huwaa"; setSelectedAppCode("huwaa"); rerender(); await act(async () => { await result.current.submitEdit({ preventDefault: vi.fn() }); }); expect(mocks.createH5Link).not.toHaveBeenCalled(); expect(mocks.updateH5Link).not.toHaveBeenCalled(); expect(mocks.showToast).toHaveBeenCalledWith("应用已切换,请重新操作", "warning"); }); test("pins an update mutation to the App that owns the edited row", async () => { const { result } = renderHook(() => useH5ConfigPage()); act(() => { result.current.openEdit({ appCode: "lalu", key: "achievement", label: "成就", updatedAtMs: 1000, url: "https://h5.example.com/achievement" }); }); await act(async () => { await result.current.submitEdit({ preventDefault: vi.fn() }); }); expect(mocks.updateH5Link).toHaveBeenCalledWith("lalu", "achievement", { key: "achievement", label: "成就", url: "https://h5.example.com/achievement" }); }); test("aborts delete when App changes while confirmation is open", async () => { let resolveConfirm; mocks.confirm.mockReturnValue(new Promise((resolve) => { resolveConfirm = resolve; })); const { result, rerender } = renderHook(() => useH5ConfigPage()); let pendingDelete; act(() => { pendingDelete = result.current.removeH5Link({ key: "achievement", label: "成就" }); }); mocks.appCode = "huwaa"; setSelectedAppCode("huwaa"); rerender(); await act(async () => { resolveConfirm(true); await pendingDelete; }); expect(mocks.deleteH5Link).not.toHaveBeenCalled(); expect(mocks.showToast).toHaveBeenCalledWith("应用已切换,请重新操作", "warning"); });