From 9574e2904b6ddab446a082b683de71228b5d09ac Mon Sep 17 00:00:00 2001 From: Ryan Lewis <93001277+rylew1@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:00:07 -0700 Subject: [PATCH] [Issue #51]: debounce pagination (#53) ## Summary Fixes #51 ## Changes proposed - Add 300ms debounce on pagination --- .../components/search/SearchPagination.tsx | 14 +++++-- frontend/tests/e2e/search/search.spec.ts | 40 ++++++++++++++----- frontend/tests/e2e/search/searchSpecUtil.ts | 5 +++ 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/frontend/src/components/search/SearchPagination.tsx b/frontend/src/components/search/SearchPagination.tsx index b8f2964a4..7ac27d984 100644 --- a/frontend/src/components/search/SearchPagination.tsx +++ b/frontend/src/components/search/SearchPagination.tsx @@ -1,6 +1,7 @@ "use client"; import { Pagination } from "@trussworks/react-uswds"; +import { useDebouncedCallback } from "use-debounce"; import { useFormStatus } from "react-dom"; export enum PaginationPosition { @@ -19,6 +20,7 @@ interface SearchPaginationProps { } const MAX_SLOTS = 7; +const DEBOUNCE_TIME = 300; export default function SearchPagination({ showHiddenInput, @@ -31,6 +33,10 @@ export default function SearchPagination({ }: SearchPaginationProps) { const { pending } = useFormStatus(); + const debouncedHandlePageChange = useDebouncedCallback((newPage: number) => { + handlePageChange(newPage); + }, DEBOUNCE_TIME); + // If there's no results, don't show pagination if (searchResultsLength < 1) { return null; @@ -59,9 +65,11 @@ export default function SearchPagination({ totalPages={totalPages} currentPage={page} maxSlots={MAX_SLOTS} - onClickNext={() => handlePageChange(page + 1)} - onClickPrevious={() => handlePageChange(page - 1)} - onClickPageNumber={(event, page) => handlePageChange(page)} + onClickNext={() => debouncedHandlePageChange(page + 1)} + onClickPrevious={() => debouncedHandlePageChange(page - 1)} + onClickPageNumber={(event: React.MouseEvent, page: number) => + debouncedHandlePageChange(page) + } /> ); diff --git a/frontend/tests/e2e/search/search.spec.ts b/frontend/tests/e2e/search/search.spec.ts index e6c69f638..e73cd256c 100644 --- a/frontend/tests/e2e/search/search.spec.ts +++ b/frontend/tests/e2e/search/search.spec.ts @@ -1,3 +1,4 @@ +import { Page, expect, test } from "@playwright/test"; import { clickAccordionWithTitle, clickLastPaginationPage, @@ -20,9 +21,18 @@ import { toggleCheckboxes, waitForSearchResultsInitialLoad, } from "./searchSpecUtil"; -import { expect, test } from "@playwright/test"; -test("should navigate from index to search page", async ({ page }) => { +import { BrowserContextOptions } from "playwright-core"; + +interface PageProps { + page: Page; + browserName?: string; + contextOptions?: BrowserContextOptions; +} + +test("should navigate from index to search page", async ({ + page, +}: PageProps) => { // Start from the index page with feature flag set await page.goto("/?_ff=showSearchV0:true"); @@ -48,7 +58,7 @@ test("should navigate from index to search page", async ({ page }) => { }); test.describe("Search page tests", () => { - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ page }: PageProps) => { // Navigate to the search page with the feature flag set await page.goto("/search?_ff=showSearchV0:true"); }); @@ -56,7 +66,7 @@ test.describe("Search page tests", () => { test("should return 0 results when searching for obscure term", async ({ page, browserName, - }) => { + }: PageProps) => { // TODO (Issue #2005): fix test for webkit test.skip( browserName === "webkit", @@ -80,7 +90,10 @@ test.describe("Search page tests", () => { ); }); - test("should show and hide loading state", async ({ page, browserName }) => { + test("should show and hide loading state", async ({ + page, + browserName, + }: PageProps) => { // TODO (Issue #2005): fix test for webkit test.skip( browserName === "webkit", @@ -98,7 +111,10 @@ test.describe("Search page tests", () => { await expect(loadingIndicator).toBeVisible(); await expect(loadingIndicator).toBeHidden(); }); - test("should refresh and retain filters in a new tab", async ({ page }) => { + + test("should refresh and retain filters in a new tab", async ({ + page, + }: PageProps) => { // Set all inputs, then refresh the page. Those same inputs should be // set from query params. const searchTerm = "education"; @@ -175,11 +191,15 @@ test.describe("Search page tests", () => { } }); - test("resets page back to 1 when choosing a filter", async ({ page }) => { + test("resets page back to 1 when choosing a filter", async ({ + page, + }: PageProps) => { await clickPaginationPageNumber(page, 2); // Verify that page 1 is highlighted - let currentPageButton = page.locator(".usa-pagination__button.usa-current"); + let currentPageButton = page + .locator(".usa-pagination__button.usa-current") + .first(); await expect(currentPageButton).toHaveAttribute("aria-label", "Page 2"); // Select the 'Closed' checkbox under 'Opportunity status' @@ -201,7 +221,7 @@ test.describe("Search page tests", () => { test("last result becomes first result when flipping sort order", async ({ page, - }) => { + }: PageProps) => { await selectSortBy(page, "opportunityTitleDesc"); await clickLastPaginationPage(page); @@ -219,7 +239,7 @@ test.describe("Search page tests", () => { test("number of results is the same with none or all opportunity status checked", async ({ page, - }) => { + }: PageProps) => { const initialNumberOfOpportunityResults = await getNumberOfOpportunitySearchResults(page); diff --git a/frontend/tests/e2e/search/searchSpecUtil.ts b/frontend/tests/e2e/search/searchSpecUtil.ts index f54d32b15..63782e670 100644 --- a/frontend/tests/e2e/search/searchSpecUtil.ts +++ b/frontend/tests/e2e/search/searchSpecUtil.ts @@ -146,6 +146,9 @@ export async function clickPaginationPageNumber( `button[data-testid="pagination-page-number"][aria-label="Page ${pageNumber}"]`, ); await paginationButton.first().click(); + + // Delay for pagination debounce + await page.waitForTimeout(400); } export async function clickLastPaginationPage(page: Page) { @@ -156,6 +159,8 @@ export async function clickLastPaginationPage(page: Page) { if (count > 2) { await paginationButtons.nth(count - 2).click(); } + // Delay for pagination debounce + await page.waitForTimeout(400); } export async function getFirstSearchResultTitle(page: Page) {