Unverified Commit 5d95dc6b authored by Lemon-miaow's avatar Lemon-miaow
Browse files

fix(panel): 玩家页四个名单加载失败时带上具体原因

parent d13d99ed
Loading
Loading
Loading
Loading
+2 −1
Changes for panel/src/components/players/BansSection.tsx: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import { cn } from "@/lib/utils";
import {
  CollapsibleSection,
  FeedbackLine,
  LoadError,
  MC_NAME,
  PagerFooter,
  PlayerField,
@@ -175,7 +176,7 @@ export function BansSection({ name }: { name: string }) {
            <Loader2 className="h-3.5 w-3.5 animate-spin" /> {t("log_connecting")}
          </div>
        ) : error ? (
          <p role="alert" className="text-xs text-destructive">{t("access_ban_load_error")}</p>
          <LoadError message={t("access_ban_load_error")} error={error} />
        ) : players.length === 0 ? (
          <div className="rounded-md border border-dashed border-border bg-muted/20 px-4 py-8 text-center">
            <p className="text-sm text-muted-foreground">{t("access_ban_empty")}</p>
+44 −0
Changes for panel/src/components/players/LoadError.test.tsx: 44 added lines, 0 removed lines.
Original line number Diff line number Diff line
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import type { ReactElement } from "react";
import i18next from "i18next";
import { humanizeError } from "@/lib/api";
import { OnlineSection } from "./OnlineSection";
import { WhitelistSection } from "./WhitelistSection";
import { BansSection } from "./BansSection";
import { WakeListSection } from "./WakeListSection";

const calls = vi.hoisted(() => ({
  accessPlayers: vi.fn(),
  accessWhitelistList: vi.fn(),
  accessBanList: vi.fn(),
  serverAllowlist: vi.fn(),
}));
vi.mock("@/lib/api", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@/lib/api")>();
  return { ...actual, api: { ...actual.api, ...calls } };
});

beforeEach(() => {
  for (const fn of Object.values(calls)) fn.mockReset();
});

// A lapsed session and a server mid-restart want different next steps, so a
// section whose list failed says which one it hit.
describe("a player list that failed to load", () => {
  it.each<[string, keyof typeof calls, string, ReactElement]>([
    ["online players", "accessPlayers", "servers:access_online_load_error", <OnlineSection name="lobby" />],
    ["whitelist", "accessWhitelistList", "servers:access_whitelist_load_error", <WhitelistSection name="lobby" />],
    ["ban list", "accessBanList", "servers:access_ban_load_error", <BansSection name="lobby" />],
    ["wake list", "serverAllowlist", "servers:wake_list_load_error", <WakeListSection name="lobby" policy="allowlist" />],
  ])("names why the %s did not load", async (_label, call, key, section) => {
    const outage = { status: 409, code: "not_running", message: "server is not running" };
    calls[call].mockRejectedValue(outage);
    render(section);

    const alert = await screen.findByRole("alert");
    expect(alert.textContent).toBe(`${i18next.t(key)} ${humanizeError(outage)}`);
    expect(humanizeError(outage)).not.toBe("");
  });
});
+2 −1
Changes for panel/src/components/players/OnlineSection.tsx: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ import { useAsync } from "@/lib/hooks";
import {
  CollapsibleSection,
  FeedbackLine,
  LoadError,
  PagerFooter,
  rconReply,
  SearchBox,
@@ -112,7 +113,7 @@ export function OnlineSection({ name, defaultOpen = true }: { name: string; defa
            <Loader2 className="h-3.5 w-3.5 animate-spin" /> {t("log_connecting")}
          </div>
        ) : error ? (
          <p role="alert" className="text-xs text-destructive">{t("access_online_load_error")}</p>
          <LoadError message={t("access_online_load_error")} error={error} />
        ) : players.length === 0 ? (
          <div className="rounded-md border border-dashed border-border bg-muted/20 px-4 py-8 text-center">
            {namesUnavailable ? (
+3 −1
Changes for panel/src/components/players/WakeListSection.test.tsx: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -103,7 +103,9 @@ describe("WakeListSection", () => {

    calls.serverAllowlist.mockRejectedValue({ status: 500, code: "internal", message: "db down" });
    render(<WakeListSection name="lobby" policy="allowlist" defaultOpen />);
    expect((await screen.findByRole("alert")).textContent).toBe("Couldn't load the wake list.");
    expect((await screen.findByRole("alert")).textContent).toBe(
      "Couldn't load the wake list. The service is unavailable right now (it may be restarting or upgrading). Try again shortly.",
    );
  });

  it("finds a player by account name or by UUID once the list is long", async () => {
+2 −4
Changes for panel/src/components/players/WakeListSection.tsx: 2 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ import { formatAbsolute, formatRelative } from "@/lib/format";
import { useAsync } from "@/lib/hooks";
import type { AllowlistEntry, AutostartPolicy } from "@/lib/types";
import { cn } from "@/lib/utils";
import { CollapsibleSection, FeedbackLine, PagerFooter, SearchBox, usePagedNames, type Feedback } from "./shared";
import { CollapsibleSection, FeedbackLine, LoadError, PagerFooter, SearchBox, usePagedNames, type Feedback } from "./shared";

/** WakeListSection shows who may wake the server while it sleeps under the "wake
 *  list" autostart policy, and lets the owner take that right away or give it back.
@@ -100,9 +100,7 @@ export function WakeListSection({
            <Loader2 className="h-3.5 w-3.5 animate-spin" /> {t("log_connecting")}
          </div>
        ) : error ? (
          <p role="alert" className="text-xs text-destructive">
            {t("wake_list_load_error")}
          </p>
          <LoadError message={t("wake_list_load_error")} error={error} />
        ) : entries.length === 0 ? (
          <div className="rounded-md border border-dashed border-border bg-muted/20 px-4 py-8 text-center">
            <p className="text-sm text-muted-foreground">{t("wake_list_empty")}</p>
Loading