Loading panel/src/pages/servers/ServersPage.test.tsx +59 −2 Changes for panel/src/pages/servers/ServersPage.test.tsx: 59 added lines, 2 removed lines. Original line number Diff line number Diff line // @vitest-environment jsdom import { describe, it, expect, vi, beforeEach, beforeAll } from "vitest"; import { render, screen, within } from "@testing-library/react"; import { describe, it, expect, vi, beforeEach, beforeAll, afterEach } from "vitest"; import { act, render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter } from "react-router-dom"; import type { FleetServer, MyServerView } from "@/lib/types"; Loading Loading @@ -341,3 +341,60 @@ describe("ServersPage order and paging", () => { expect(await tableOrder()).toEqual(names.slice(0, 20)); }); }); describe("ServersPage with nothing matching", () => { afterEach(() => { vi.useRealTimers(); }); it("clears the search and the phase filter from the empty state", async () => { tier.isAdmin = false; const mine = (name: string): MyServerView => ({ name, subdomain: name, owned: true, claimable: false, phase: "Running", playersOnline: 0, playersMax: 20, }); calls.myServers.mockResolvedValue([mine("survival"), mine("creative")]); const user = renderPage(); await tableOrder(); const search = screen.getByPlaceholderText(/Search/) as HTMLInputElement; await user.type(search, "zzz"); await user.click(screen.getByRole("combobox", { name: "Filter by phase" })); await user.click(await screen.findByRole("option", { name: "Stopping" })); expect(screen.getByText("No matching servers found.")).toBeTruthy(); expect(screen.getByText("No server matches the current search or filter.")).toBeTruthy(); await user.click(screen.getByRole("button", { name: "Clear filters" })); expect(await tableOrder()).toEqual(["creative", "survival"]); expect(search.value).toBe(""); expect(screen.getByRole("combobox", { name: "Filter by phase" }).textContent).toBe("All phases"); }); it("starts the cleared list from its first page", async () => { vi.useFakeTimers({ shouldAdvanceTime: true }); const names = Array.from({ length: 21 }, (_, i) => `srv-${String(i + 1).padStart(2, "0")}`); calls.fleet.mockResolvedValue(names.map((n) => row(n, { phase: "Running", ready: true }))); const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); render( <MemoryRouter> <ServersPage /> </MemoryRouter>, ); await tableOrder(); await user.click(screen.getByRole("combobox", { name: "Filter by phase" })); await user.click(await screen.findByRole("option", { name: "Running" })); await user.click(screen.getByRole("button", { name: /Next/ })); expect(await tableOrder()).toEqual(["srv-21"]); // The next reread finds every server stopped, so the filtered page empties. calls.fleet.mockResolvedValue(names.map((n) => row(n, {}))); await act(() => vi.advanceTimersByTimeAsync(10_000)); await user.click(await screen.findByRole("button", { name: "Clear filters" })); expect(await tableOrder()).toEqual(names.slice(0, 20)); }); }); panel/src/pages/servers/ServersPage.tsx +14 −2 Changes for panel/src/pages/servers/ServersPage.tsx: 14 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -402,8 +402,20 @@ export function ServersPage() { ) : visible.length === 0 ? ( <EmptyState title={isAdmin ? t("fleet_no_match_title") : t("servers:search_no_match")} hint={isAdmin ? t("fleet_no_match_hint") : t("servers:search_clear_btn")} /> hint={t("fleet_no_match_hint")} > <Button variant="outline" size="sm" onClick={() => { setQuery(""); setPhaseFilter("all"); setPage(1); }} > {t("servers:search_clear_btn")} </Button> </EmptyState> ) : ( <> {/* Cards until the table fits (two per row from md). The admin Loading Loading
panel/src/pages/servers/ServersPage.test.tsx +59 −2 Changes for panel/src/pages/servers/ServersPage.test.tsx: 59 added lines, 2 removed lines. Original line number Diff line number Diff line // @vitest-environment jsdom import { describe, it, expect, vi, beforeEach, beforeAll } from "vitest"; import { render, screen, within } from "@testing-library/react"; import { describe, it, expect, vi, beforeEach, beforeAll, afterEach } from "vitest"; import { act, render, screen, within } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { MemoryRouter } from "react-router-dom"; import type { FleetServer, MyServerView } from "@/lib/types"; Loading Loading @@ -341,3 +341,60 @@ describe("ServersPage order and paging", () => { expect(await tableOrder()).toEqual(names.slice(0, 20)); }); }); describe("ServersPage with nothing matching", () => { afterEach(() => { vi.useRealTimers(); }); it("clears the search and the phase filter from the empty state", async () => { tier.isAdmin = false; const mine = (name: string): MyServerView => ({ name, subdomain: name, owned: true, claimable: false, phase: "Running", playersOnline: 0, playersMax: 20, }); calls.myServers.mockResolvedValue([mine("survival"), mine("creative")]); const user = renderPage(); await tableOrder(); const search = screen.getByPlaceholderText(/Search/) as HTMLInputElement; await user.type(search, "zzz"); await user.click(screen.getByRole("combobox", { name: "Filter by phase" })); await user.click(await screen.findByRole("option", { name: "Stopping" })); expect(screen.getByText("No matching servers found.")).toBeTruthy(); expect(screen.getByText("No server matches the current search or filter.")).toBeTruthy(); await user.click(screen.getByRole("button", { name: "Clear filters" })); expect(await tableOrder()).toEqual(["creative", "survival"]); expect(search.value).toBe(""); expect(screen.getByRole("combobox", { name: "Filter by phase" }).textContent).toBe("All phases"); }); it("starts the cleared list from its first page", async () => { vi.useFakeTimers({ shouldAdvanceTime: true }); const names = Array.from({ length: 21 }, (_, i) => `srv-${String(i + 1).padStart(2, "0")}`); calls.fleet.mockResolvedValue(names.map((n) => row(n, { phase: "Running", ready: true }))); const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); render( <MemoryRouter> <ServersPage /> </MemoryRouter>, ); await tableOrder(); await user.click(screen.getByRole("combobox", { name: "Filter by phase" })); await user.click(await screen.findByRole("option", { name: "Running" })); await user.click(screen.getByRole("button", { name: /Next/ })); expect(await tableOrder()).toEqual(["srv-21"]); // The next reread finds every server stopped, so the filtered page empties. calls.fleet.mockResolvedValue(names.map((n) => row(n, {}))); await act(() => vi.advanceTimersByTimeAsync(10_000)); await user.click(await screen.findByRole("button", { name: "Clear filters" })); expect(await tableOrder()).toEqual(names.slice(0, 20)); }); });
panel/src/pages/servers/ServersPage.tsx +14 −2 Changes for panel/src/pages/servers/ServersPage.tsx: 14 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -402,8 +402,20 @@ export function ServersPage() { ) : visible.length === 0 ? ( <EmptyState title={isAdmin ? t("fleet_no_match_title") : t("servers:search_no_match")} hint={isAdmin ? t("fleet_no_match_hint") : t("servers:search_clear_btn")} /> hint={t("fleet_no_match_hint")} > <Button variant="outline" size="sm" onClick={() => { setQuery(""); setPhaseFilter("all"); setPage(1); }} > {t("servers:search_clear_btn")} </Button> </EmptyState> ) : ( <> {/* Cards until the table fits (two per row from md). The admin Loading