import { afterEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen, waitFor, within, } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { TestDataProvider } from "../../test/providerCompat.jsx"; import { MemoryRouter } from "../../test/routerCompat.jsx"; import { GatewayList } from "./GatewaysPage"; afterEach(() => { cleanup(); vi.restoreAllMocks(); }); const gateways = [ { id: "gw-1", name: "gw-1", endpoint: "http://127.0.0.1:8081", token: "unit-test-gateway-token", }, { id: "gw-2", name: "gw-2", endpoint: "http://gw2:8081", token: "offline-token-123456", }, ]; function provider(overrides = {}) { return { getList: vi.fn(() => Promise.resolve({ data: gateways, total: gateways.length }), ), create: vi.fn().mockResolvedValue({ data: { name: "gw-2", endpoint: "http://gw2:8081", token: "generated-token-abcdef", }, }), deleteOne: vi.fn().mockResolvedValue({ data: { id: "gw-1" } }), getOne: vi.fn(), getMany: vi.fn(), getManyReference: vi.fn(), update: vi.fn(), updateMany: vi.fn(), deleteMany: vi.fn(), browserAction: vi.fn(), ...overrides, }; } const renderGateways = (dataProvider) => render( , ); describe("GatewayList", () => { it("registers a gateway and reveals the generated token", async () => { const dataProvider = provider(); renderGateways(dataProvider); await screen.findAllByText("gw-1"); fireEvent.click(screen.getByRole("button", { name: "注册网关" })); const dialog = screen.getByRole("dialog"); fireEvent.change(within(dialog).getByRole("textbox", { name: "名称" }), { target: { value: "gw-2" }, }); fireEvent.change( within(dialog).getByRole("textbox", { name: "Endpoint" }), { target: { value: "http://gw2:8081" } }, ); fireEvent.click(within(dialog).getByRole("button", { name: "注册网关" })); await waitFor(() => expect(dataProvider.create).toHaveBeenCalledWith({ resource: "gateways", variables: { name: "gw-2", endpoint: "http://gw2:8081", token: "" }, }), ); expect((await screen.findByRole("alert")).textContent).toContain( "generated-token-abcdef", ); await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull()); fireEvent.click(screen.getByRole("button", { name: "注册网关" })); const reopened = screen.getByRole("dialog"); expect(within(reopened).getByRole("textbox", { name: "名称" }).value).toBe( "", ); expect( within(reopened).getByRole("textbox", { name: "Endpoint" }).value, ).toBe(""); }); it("keeps the registration modal open after a failure", async () => { const dataProvider = provider({ create: vi.fn().mockRejectedValue(new Error("网关名称已存在")), }); renderGateways(dataProvider); await screen.findAllByText("gw-1"); fireEvent.click(screen.getByRole("button", { name: "注册网关" })); const dialog = screen.getByRole("dialog"); fireEvent.change(within(dialog).getByRole("textbox", { name: "名称" }), { target: { value: "gw-2" }, }); fireEvent.change( within(dialog).getByRole("textbox", { name: "Endpoint" }), { target: { value: "http://gw2:8081" } }, ); fireEvent.click(within(dialog).getByRole("button", { name: "注册网关" })); expect((await within(dialog).findByRole("alert")).textContent).toContain( "网关名称已存在", ); expect(screen.getByRole("dialog")).toBeTruthy(); }); it("rejects an invalid endpoint before submit", async () => { const dataProvider = provider(); renderGateways(dataProvider); await screen.findAllByText("gw-1"); fireEvent.click(screen.getByRole("button", { name: "注册网关" })); const dialog = screen.getByRole("dialog"); fireEvent.change(within(dialog).getByRole("textbox", { name: "名称" }), { target: { value: "gw-2" }, }); fireEvent.change( within(dialog).getByRole("textbox", { name: "Endpoint" }), { target: { value: "ftp://gw2:8081" } }, ); expect( within(dialog).getByRole("button", { name: "注册网关" }).disabled, ).toBe(true); expect(dataProvider.create).not.toHaveBeenCalled(); }); it("edits and renames a gateway", async () => { const update = vi.fn().mockResolvedValue({ data: { id: "gw-main" } }); const dataProvider = provider({ update }); renderGateways(dataProvider); fireEvent.click(await screen.findByLabelText("编辑 gw-1")); const dialog = screen.getByRole("dialog"); const name = within(dialog).getByRole("textbox", { name: "名称" }); const endpoint = within(dialog).getByRole("textbox", { name: "Endpoint" }); expect(name.value).toBe("gw-1"); expect(endpoint.value).toBe("http://127.0.0.1:8081"); fireEvent.change(name, { target: { value: "gw-main" } }); fireEvent.change(endpoint, { target: { value: "http://gw-main:8081" } }); fireEvent.change( within(dialog).getByRole("textbox", { name: "令牌(留空保持不变)" }), { target: { value: "rotated-gateway-token" }, }, ); fireEvent.click(within(dialog).getByRole("button", { name: "保存" })); await waitFor(() => expect(update).toHaveBeenCalledWith({ resource: "gateways", id: "gw-1", variables: { name: "gw-main", endpoint: "http://gw-main:8081", token: "rotated-gateway-token", }, }), ); expect(dataProvider.getList).toHaveBeenCalledTimes(2); }); it("shows the edit failure without closing the form", async () => { const update = vi.fn().mockRejectedValue(new Error("网关名称已存在")); renderGateways(provider({ update })); fireEvent.click(await screen.findByLabelText("编辑 gw-1")); const dialog = screen.getByRole("dialog"); fireEvent.click(within(dialog).getByRole("button", { name: "保存" })); expect((await within(dialog).findByRole("alert")).textContent).toContain( "网关名称已存在", ); expect(screen.getByRole("dialog")).toBeTruthy(); }); it("deletes a gateway after confirm", async () => { const dataProvider = provider(); renderGateways(dataProvider); fireEvent.click(await screen.findByLabelText("删除 gw-1")); fireEvent.click(await screen.findByRole("button", { name: "确认删除" })); await waitFor(() => expect(dataProvider.deleteOne).toHaveBeenCalledWith({ resource: "gateways", id: "gw-1", }), ); }); it("renders labeled actions in a right-aligned operation cell", async () => { renderGateways(provider()) const table = await screen.findByRole("table") expect(within(table).getByRole("columnheader", { name: "操作" }).className).toContain("text-right") const edit = within(table).getByRole("button", { name: "编辑 gw-1" }) const remove = within(table).getByRole("button", { name: "删除 gw-1" }) expect(edit.textContent).toContain("编辑") expect(remove.textContent).toContain("删除") expect(edit.closest("td").className).toContain("text-right") }) it("does not render live gateway status in the list", async () => { renderGateways(provider()); const table = await screen.findByRole("table"); expect(within(table).queryByText("连通性")).toBeNull(); expect(within(table).queryByText("健康")).toBeNull(); expect(within(table).queryByText("在线")).toBeNull(); expect(within(table).queryByText("离线")).toBeNull(); expect(within(table).queryByText("异常")).toBeNull(); }); });