feat: enhance server handling and add empty state messaging in Server component

This commit is contained in:
hamster1963
2026-07-12 00:41:56 +08:00
parent 27d8d3f572
commit 7821ce1131
12 changed files with 199 additions and 40 deletions
+45 -4
View File
@@ -132,7 +132,10 @@ function WebSocketProbe() {
<div>
<p>{connected ? "connected" : "disconnected"}</p>
<p>{lastData?.servers[0]?.name ?? "none"}</p>
<p>{messageHistory.length}</p>
<p data-testid="message-count">{messageHistory.length}</p>
<p data-testid="history-server-count">
{messageHistory.reduce((total, item) => total + item.servers.length, 0)}
</p>
<p>{needReconnect ? "needs-reconnect" : "stable"}</p>
<button type="button" onClick={() => setNeedReconnect(true)}>
mark
@@ -268,7 +271,24 @@ describe("WebSocketProvider", () => {
});
expect(screen.getByText("second")).toBeInTheDocument();
expect(screen.getByText("2")).toBeInTheDocument();
expect(screen.getByTestId("message-count")).toHaveTextContent("2");
});
it("normalizes missing and non-array server collections", () => {
renderWebSocketProvider(<WebSocketProbe />);
const socket = FakeWebSocket.instances[0];
const now = Date.parse("2025-01-01T00:00:20.000Z");
act(() => {
socket.open();
socket.message(JSON.stringify({ now }));
socket.message(JSON.stringify({ now, servers: null }));
socket.message(JSON.stringify({ now, servers: { invalid: true } }));
});
expect(screen.getByText("none")).toBeInTheDocument();
expect(screen.getByTestId("message-count")).toHaveTextContent("3");
expect(screen.getByTestId("history-server-count")).toHaveTextContent("0");
});
it("keeps only the latest thirty websocket messages", () => {
@@ -283,7 +303,7 @@ describe("WebSocketProvider", () => {
});
expect(screen.getByText("message-30")).toBeInTheDocument();
expect(screen.getByText("30")).toBeInTheDocument();
expect(screen.getByTestId("message-count")).toHaveTextContent("30");
});
it("ignores malformed websocket messages without disconnecting", () => {
@@ -300,13 +320,34 @@ describe("WebSocketProvider", () => {
expect(screen.getByText("connected")).toBeInTheDocument();
expect(screen.getByText("none")).toBeInTheDocument();
expect(screen.getByText("0")).toBeInTheDocument();
expect(screen.getByTestId("message-count")).toHaveTextContent("0");
expect(consoleError).toHaveBeenCalledWith(
"Failed to parse WebSocket message:",
expect.any(SyntaxError),
);
});
it("ignores websocket messages without a valid response shape", () => {
const consoleError = vi
.spyOn(console, "error")
.mockImplementation(() => undefined);
renderWebSocketProvider(<WebSocketProbe />);
const socket = FakeWebSocket.instances[0];
act(() => {
socket.open();
socket.message("null");
socket.message(JSON.stringify({ servers: [] }));
});
expect(screen.getByTestId("message-count")).toHaveTextContent("0");
expect(consoleError).toHaveBeenCalledTimes(2);
expect(consoleError).toHaveBeenCalledWith(
"Failed to parse WebSocket message:",
expect.any(TypeError),
);
});
it("exposes manual reconnect state separately from socket state", async () => {
const user = userEvent.setup();
renderWebSocketProvider(<WebSocketProbe />);
+39
View File
@@ -241,6 +241,45 @@ describe("Servers page", () => {
});
});
it("shows an empty state and hides controls when there are no servers", () => {
const { container } = renderServerPage({
connected: true,
lastData: websocketPayload([]),
});
expect(screen.getByTestId("server-overview")).toHaveTextContent("0:0:0");
expect(screen.getByText("info.noServers")).toBeInTheDocument();
expect(
container.querySelector(".server-overview-controls"),
).not.toBeVisible();
expect(screen.queryByTestId("server-card")).not.toBeInTheDocument();
expect(screen.queryByTestId("global-map")).not.toBeInTheDocument();
expect(screen.queryByTestId("service-tracker")).not.toBeInTheDocument();
});
it("shows a filtered empty state while keeping controls available", async () => {
const offline = createServer({
id: 1,
name: "offline",
last_active: "2024-12-31T23:00:00.000Z",
});
const user = userEvent.setup();
const { container } = renderServerPage(
{
connected: true,
lastData: websocketPayload([offline]),
},
{ withStatusControl: true },
);
await user.click(screen.getByRole("button", { name: "online-only" }));
expect(screen.getByText("info.noMatchingServers")).toBeInTheDocument();
expect(container.querySelector(".server-overview-controls")).not.toBeNull();
expect(screen.queryByTestId("server-card")).not.toBeInTheDocument();
});
it("filters servers by selected group", async () => {
const online = createServer({ id: 1, name: "alpha" });
const offline = createServer({