fix(panel): 搜索框的停顿计时只在搜索词变了才起,列表刚加载就翻的页不再被拉回第一页
This commit is contained in:
6 files changed
+72
-9
No files matched your search
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
() =>
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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 }) =>
|
||||
|
||||
@@ -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(
|
||||
() =>
|
||||
|
||||
Reference in new issue
Block a user