fix(panel): 搜索框的停顿计时只在搜索词变了才起,列表刚加载就翻的页不再被拉回第一页

This commit is contained in:
Lemon-miaow committed 2026-09-29 03:25:26 +08:00
1 parent 3c0322a5c6
commit 6644046016
6 files changed
+72 -9

No files matched your search

+21 -1
View File
@@ -1,6 +1,6 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { act, render, screen } from "@testing-library/react";
import { act, fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
import i18next from "i18next";
@@ -78,6 +78,26 @@ describe("MySubmissionsPage", () => {
await vi.waitFor(() => expect(screen.queryByText(/^Page \d+ of/)).toBeNull());
});
// The clock moves only when the test moves it, so the page is turned before
// the search box's pause has run out however fast the machine is.
it("keeps a page turned to right after the list loads", async () => {
vi.useFakeTimers();
render(
<MemoryRouter>
<MySubmissionsPage />
</MemoryRouter>,
);
await act(() => vi.advanceTimersByTimeAsync(0));
expect(screen.getByText("Page 1 of 2")).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: "Next" }));
await act(() => vi.advanceTimersByTimeAsync(0));
expect(calls.listMySubmissions).toHaveBeenLastCalledWith({ limit: 10, offset: 10 });
await act(() => vi.advanceTimersByTimeAsync(1_000));
expect(calls.listMySubmissions).toHaveBeenLastCalledWith({ limit: 10, offset: 10 });
expect(screen.getByText("Page 2 of 2")).toBeTruthy();
});
it("refuses a file over the server's per-upload cap before sending it", async () => {
render(
<MemoryRouter>
+6 -2
View File
@@ -113,13 +113,17 @@ export function MySubmissionsPage() {
const [statusFilter, setStatusFilter] = useState<"all" | SubmissionStatus>("all");
const [page, setPage] = useState(1);
const [expandedId, setExpandedId] = useState<string | null>(null);
// A search already asked (the first render's included) starts no timer, so
// a page turned just after the list loads is not sent back to page one.
useEffect(() => {
const next = search.trim();
if (next === query) return;
const timer = setTimeout(() => {
setQuery(search.trim());
setQuery(next);
setPage(1);
}, SEARCH_DEBOUNCE_MS);
return () => clearTimeout(timer);
}, [search]);
}, [search, query]);
const listMine = useCallback(
() =>
+16 -1
View File
@@ -1,6 +1,6 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from "vitest";
import { render, screen, within } from "@testing-library/react";
import { act, fireEvent, render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
import i18next from "i18next";
@@ -100,6 +100,21 @@ describe("ImageBuildPage list", () => {
await vi.waitFor(() => expect(calls.listBuilds).toHaveBeenLastCalledWith({ query: undefined, limit: 10, offset: 10 }));
});
// The clock moves only when the test moves it, so the page is turned before
// the search box's pause has run out however fast the machine is.
it("keeps a page turned to right after the list loads", async () => {
vi.useFakeTimers();
calls.listBuilds.mockResolvedValue(page([MINE], 25));
renderPage();
await act(() => vi.advanceTimersByTimeAsync(0));
expect(screen.getByText(MINE.image_ref)).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: t("common:pagination_next") }));
await act(() => vi.advanceTimersByTimeAsync(0));
expect(calls.listBuilds).toHaveBeenLastCalledWith({ query: undefined, limit: 10, offset: 10 });
await act(() => vi.advanceTimersByTimeAsync(1_000));
expect(calls.listBuilds).toHaveBeenLastCalledWith({ query: undefined, limit: 10, offset: 10 });
});
it("searches on the server, sending a status typed as the badge shows it by its code, from page one", async () => {
await i18next.changeLanguage("zh-CN");
calls.listBuilds.mockResolvedValue(page([MINE], 25));
+6 -2
View File
@@ -158,13 +158,17 @@ export function ImageBuildPage() {
const [search, setSearch] = useState("");
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
// A search already asked (the first render's included) starts no timer, so
// a page turned just after the list loads is not sent back to page one.
useEffect(() => {
const next = serverQuery(search, t);
if (next === query) return;
const timer = setTimeout(() => {
setQuery(serverQuery(search, t));
setQuery(next);
setPage(1);
}, SEARCH_DEBOUNCE_MS);
return () => clearTimeout(timer);
}, [search, t]);
}, [search, t, query]);
const listBuilds = useCallback(
() => api.listBuilds({ query: query || undefined, limit: PAGE_SIZE, offset: (page - 1) * PAGE_SIZE }),
+17 -1
View File
@@ -1,6 +1,6 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen } from "@testing-library/react";
import { act, fireEvent, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { MemoryRouter } from "react-router-dom";
import i18next from "i18next";
@@ -38,6 +38,7 @@ beforeEach(() => {
calls.listSubmissions.mockResolvedValue(PAGE);
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
return i18next.changeLanguage("en-US");
});
@@ -84,6 +85,21 @@ describe("SubmissionsPage", () => {
);
});
// The clock moves only when the test moves it, so the page is turned before
// the search box's pause has run out however fast the machine is.
it("keeps a page turned to right after the queue loads", async () => {
vi.useFakeTimers();
renderPage();
await act(() => vi.advanceTimersByTimeAsync(0));
expect(screen.getByText("Page 1 of 3")).toBeTruthy();
fireEvent.click(screen.getByRole("button", { name: "Next" }));
await act(() => vi.advanceTimersByTimeAsync(0));
expect(calls.listSubmissions).toHaveBeenLastCalledWith({ limit: 10, offset: 10 });
await act(() => vi.advanceTimersByTimeAsync(1_000));
expect(calls.listSubmissions).toHaveBeenLastCalledWith({ limit: 10, offset: 10 });
expect(screen.getByText("Page 2 of 3")).toBeTruthy();
});
it("steps back to the last page that still has rows when its own page comes back empty", async () => {
// Reviews elsewhere shrank the queue to 20 rows: page 3 no longer exists.
calls.listSubmissions.mockImplementation(async ({ offset }: { offset: number }) =>
+6 -2
View File
@@ -60,13 +60,17 @@ export function SubmissionsPage() {
const [statusFilter, setStatusFilter] = useState<"all" | SubmissionStatus>("all");
const [page, setPage] = useState(1);
const [expandedId, setExpandedId] = useState<string | null>(null);
// A search already asked (the first render's included) starts no timer, so
// a page turned just after the queue loads is not sent back to page one.
useEffect(() => {
const next = search.trim();
if (next === query) return;
const timer = setTimeout(() => {
setQuery(search.trim());
setQuery(next);
setPage(1);
}, SEARCH_DEBOUNCE_MS);
return () => clearTimeout(timer);
}, [search]);
}, [search, query]);
const listSubmissions = useCallback(
() =>