From b4954ae614610a1f54da470d2935c69170dd4e4d Mon Sep 17 00:00:00 2001 From: acouch Date: Fri, 5 Jul 2024 17:23:30 -0400 Subject: [PATCH] Update tests --- .../search/SearchOpportunityStatus.tsx | 14 +---- .../components/search/SearchPagination.tsx | 2 +- .../tests/api/OpportunityListingApi.test.ts | 4 +- .../components/search/SearchBar.test.tsx | 26 ++++++-- .../SearchFilterAccordion.test.tsx | 61 +++++++++++++------ .../SearchFilterCheckbox.test.tsx | 21 +------ .../SearchFilterSection.test.tsx | 23 ------- .../search/SearchOpportunityStatus.test.tsx | 49 ++++++--------- .../search/SearchPagination.test.tsx | 52 ++++++++-------- .../search/SearchResultsHeader.test.tsx | 6 +- .../components/search/SearchSortBy.test.tsx | 20 +++--- .../tests/hooks/useSearchParamUpdater.test.ts | 47 ++++++-------- 12 files changed, 142 insertions(+), 183 deletions(-) diff --git a/frontend/src/components/search/SearchOpportunityStatus.tsx b/frontend/src/components/search/SearchOpportunityStatus.tsx index 38eae20335..b34621809b 100644 --- a/frontend/src/components/search/SearchOpportunityStatus.tsx +++ b/frontend/src/components/search/SearchOpportunityStatus.tsx @@ -2,7 +2,6 @@ import { Checkbox } from "@trussworks/react-uswds"; import { QueryContext } from "../../app/[locale]/search/QueryProvider"; import { useContext } from "react"; -import { useDebouncedCallback } from "use-debounce"; import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; interface StatusOption { @@ -22,27 +21,16 @@ const statusOptions: StatusOption[] = [ { id: "status-archived", label: "Archived", value: "archived" }, ]; -// Wait 50 miliseconds before updating query params -// and submitting the form -const SEARCH_OPPORTUNITY_STATUS_DEBOUNCE_TIME = 50; - export default function SearchOpportunityStatus({ query, }: SearchOpportunityStatusProps) { const { queryTerm } = useContext(QueryContext); const { updateQueryParams } = useSearchParamUpdater(); - const debouncedUpdate = useDebouncedCallback( - (selectedStatuses: Set) => { - updateQueryParams(selectedStatuses, "status", queryTerm); - }, - SEARCH_OPPORTUNITY_STATUS_DEBOUNCE_TIME, - ); - const handleCheck = (value: string, isChecked: boolean) => { const updated = new Set(query); isChecked ? updated.add(value) : updated.delete(value); - debouncedUpdate(updated); + updateQueryParams(updated, "status", queryTerm); }; return ( diff --git a/frontend/src/components/search/SearchPagination.tsx b/frontend/src/components/search/SearchPagination.tsx index 03ea044447..a23a30ea15 100644 --- a/frontend/src/components/search/SearchPagination.tsx +++ b/frontend/src/components/search/SearchPagination.tsx @@ -1,8 +1,8 @@ "use client"; import { Pagination } from "@trussworks/react-uswds"; import { QueryContext } from "src/app/[locale]/search/QueryProvider"; -import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; import { useContext } from "react"; +import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; export enum PaginationPosition { Top = "topPagination", diff --git a/frontend/tests/api/OpportunityListingApi.test.ts b/frontend/tests/api/OpportunityListingApi.test.ts index 03bb71c438..43a08e4497 100644 --- a/frontend/tests/api/OpportunityListingApi.test.ts +++ b/frontend/tests/api/OpportunityListingApi.test.ts @@ -1,7 +1,7 @@ import { ApiResponse } from "../../src/types/opportunity/opportunityResponseTypes"; import OpportunityListingAPI from "../../src/app/api/OpportunityListingAPI"; -jest.mock("../../src/app/api/BaseApi"); +jest.mock("src/app/api/BaseApi"); describe("OpportunityListingAPI", () => { const mockedRequest = jest.fn(); @@ -21,7 +21,7 @@ describe("OpportunityListingAPI", () => { mockedRequest.mockResolvedValue(mockResponse); const result = await opportunityListingAPI.getOpportunityById(12345); - console.log("results => ", result); + expect(mockedRequest).toHaveBeenCalledWith( "GET", opportunityListingAPI.basePath, diff --git a/frontend/tests/components/search/SearchBar.test.tsx b/frontend/tests/components/search/SearchBar.test.tsx index 0b7b1d201f..1bca58a251 100644 --- a/frontend/tests/components/search/SearchBar.test.tsx +++ b/frontend/tests/components/search/SearchBar.test.tsx @@ -5,6 +5,7 @@ import { fireEvent, render, screen } from "@testing-library/react"; import React from "react"; import SearchBar from "src/components/search/SearchBar"; import { axe } from "jest-axe"; +import QueryProvider from "src/app/[locale]/search/QueryProvider"; // Mock the hook since it's used in the component const mockUpdateQueryParams = jest.fn(); @@ -19,13 +20,21 @@ describe("SearchBar", () => { const initialQueryParams = "initial query"; it("should not have basic accessibility issues", async () => { - const { container } = render(); + const { container } = render( + + + , + ); const results = await axe(container); expect(results).toHaveNoViolations(); }); it("updates the input value when typing in the search field", () => { - render(); + render( + + + , + ); const input = screen.getByRole("searchbox"); fireEvent.change(input, { target: { value: "new query" } }); @@ -34,7 +43,11 @@ describe("SearchBar", () => { }); it("calls updateQueryParams with the correct argument when submitting the form", () => { - render(); + render( + + + , + ); const input = screen.getByRole("searchbox"); fireEvent.change(input, { target: { value: "new query" } }); @@ -42,6 +55,11 @@ describe("SearchBar", () => { const searchButton = screen.getByRole("button", { name: /search/i }); fireEvent.click(searchButton); - expect(mockUpdateQueryParams).toHaveBeenCalledWith("new query", "query"); + expect(mockUpdateQueryParams).toHaveBeenCalledWith( + "", + "query", + "new query", + false, + ); }); }); diff --git a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterAccordion.test.tsx b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterAccordion.test.tsx index c4b90cc2b0..d93b1806a7 100644 --- a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterAccordion.test.tsx +++ b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterAccordion.test.tsx @@ -1,12 +1,10 @@ import "@testing-library/jest-dom/extend-expect"; - +import { axe } from "jest-axe"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; import SearchFilterAccordion, { FilterOption, } from "src/components/search/SearchFilterAccordion/SearchFilterAccordion"; -import { fireEvent, render, screen } from "@testing-library/react"; - -import React from "react"; -import { axe } from "jest-axe"; const initialFilterOptions: FilterOption[] = [ { @@ -31,9 +29,17 @@ const initialFilterOptions: FilterOption[] = [ }, ]; +const mockUpdateQueryParams = jest.fn(); + +jest.mock("src/hooks/useSearchParamUpdater", () => ({ + useSearchParamUpdater: () => ({ + updateQueryParams: mockUpdateQueryParams, + }), +})); + describe("SearchFilterAccordion", () => { const title = "Test Accordion"; - const queryParamKey = "status"; + const queryParamKey = "fundingInstrument"; it("should not have basic accessibility issues", async () => { const { container } = render( @@ -65,7 +71,7 @@ describe("SearchFilterAccordion", () => { }); it("displays select all and clear all correctly", () => { - render( + const { rerender } = render( { "Cooperative Agreement", ); - // after clicking one of the boxes, both select all and clear all should be enabled - fireEvent.click(cooperativeAgreementCheckbox); + const updatedQuery = new Set(""); + updatedQuery.add("Cooperative Agreement"); + // after clicking one of the boxes, the page should rerender + // both select all and clear all should be enabled + rerender( + , + ); expect(selectAllButton).toBeEnabled(); expect(clearAllButton).toBeEnabled(); }); @@ -97,7 +113,7 @@ describe("SearchFilterAccordion", () => { , ); @@ -116,31 +132,38 @@ describe("SearchFilterAccordion", () => { }); it("checks boxes correctly and updates count", () => { - render( + const { rerender } = render( , + ); + + const updatedQuery = new Set(""); + updatedQuery.add("Cooperative Agreement"); + updatedQuery.add("Grant"); + // after clicking one of the boxes, the page should rerender + // both select all and clear all should be enabled + rerender( + , ); const cooperativeAgreementCheckbox = screen.getByLabelText( "Cooperative Agreement", ); - fireEvent.click(cooperativeAgreementCheckbox); - const grantCheckbox = screen.getByLabelText("Grant"); - fireEvent.click(grantCheckbox); // Verify the count updates to 2 const countSpan = screen.getByText("2", { selector: ".usa-tag.usa-tag--big.radius-pill.margin-left-1", }); expect(countSpan).toBeInTheDocument(); - - // Verify the checkboxes are checked - expect(cooperativeAgreementCheckbox).toBeChecked(); - expect(grantCheckbox).toBeChecked(); }); }); diff --git a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterCheckbox.test.tsx b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterCheckbox.test.tsx index 5ab8adf6dc..fd7e9c3e4e 100644 --- a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterCheckbox.test.tsx +++ b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterCheckbox.test.tsx @@ -42,28 +42,9 @@ describe("SearchFilterCheckbox", () => { const checkbox = screen.getByLabelText(option.label); fireEvent.click(checkbox); - // Wait for the increment function to be called - await waitFor(() => { - expect(mockIncrement).toHaveBeenCalledTimes(1); - }); - // Wait for the updateCheckedOption function to be called with the checkbox being checked await waitFor(() => { - expect(mockUpdateCheckedOption).toHaveBeenCalledWith(option.id, true); + expect(mockUpdateCheckedOption).toHaveBeenCalledWith(option.value, true); }); - - // Simulate user clicking the checkbox again to uncheck it - fireEvent.click(checkbox); - - // TODO (Issue #1618): Resolve issues with unchecking - - // await waitFor(() => { - // expect(mockDecrement).toHaveBeenCalledTimes(1); - // }); - - // // Wait for the updateCheckedOption function to be called with the checkbox being unchecked - // await waitFor(() => { - // expect(mockUpdateCheckedOption).toHaveBeenCalledWith(option.id, false); - // }); }); }); diff --git a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.test.tsx b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.test.tsx index 405b9b84c6..57fdac05bb 100644 --- a/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.test.tsx +++ b/frontend/tests/components/search/SearchFilterAccordion/SearchFilterSection/SearchFilterSection.test.tsx @@ -80,29 +80,6 @@ describe("SearchFilterSection", () => { ).toBeInTheDocument(); }); - it("correctly updates the section count", async () => { - // Render the component with default props - render(); - - // Just 1 is checked initially, so count should be 1 - const countSpanAsOne = screen.getByText("1", { selector: ".usa-tag" }); - expect(countSpanAsOne).toBeInTheDocument(); - - // uncollapse section - fireEvent.click(screen.getByRole("button")); - - // Check the 1st box in the section - const checkboxForChild1 = screen.getByLabelText("Child 1"); - fireEvent.click(checkboxForChild1); - - await waitFor(() => { - // count should now be 2 - expect( - screen.getByText("2", { selector: ".usa-tag" }), - ).toBeInTheDocument(); - }); - }); - it("renders hidden inputs for checked children when collapsed", () => { // checkbox "1-2" is checked, but when the section is collapsed we still need to send the value // to the form to submit, so we use a hidden input. It must be present. diff --git a/frontend/tests/components/search/SearchOpportunityStatus.test.tsx b/frontend/tests/components/search/SearchOpportunityStatus.test.tsx index 38ddf92fb1..44685a10d5 100644 --- a/frontend/tests/components/search/SearchOpportunityStatus.test.tsx +++ b/frontend/tests/components/search/SearchOpportunityStatus.test.tsx @@ -1,16 +1,8 @@ import "@testing-library/jest-dom/extend-expect"; - -import { render, screen } from "@testing-library/react"; - +import { axe } from "jest-axe"; +import { fireEvent, render, screen } from "@testing-library/react"; import React from "react"; import SearchOpportunityStatus from "src/components/search/SearchOpportunityStatus"; -import { axe } from "jest-axe"; - -jest.mock("use-debounce", () => ({ - useDebouncedCallback: (fn: (...args: unknown[]) => unknown) => { - return [fn, jest.fn()]; - }, -})); const mockUpdateQueryParams = jest.fn(); @@ -37,28 +29,23 @@ describe("SearchOpportunityStatus", () => { expect(screen.getByText("Archived")).toBeEnabled(); }); - /* eslint-disable jest/no-commented-out-tests */ - - // TODO: Fix additional tests - - // it("checking a checkbox calls updateQueryParams and requestSubmit", async () => { - // render(); + it("checking a checkbox calls updateQueryParams and requestSubmit", async () => { + const query = new Set(""); + query.add("test"); + const combined = new Set(""); + combined.add("test").add("forecasted"); + render(); - // // No need to wait for component to mount since we're not testing that here - // const forecastedCheckbox = screen.getByRole("checkbox", { - // name: "Forecasted", - // }); + const forecastedCheckbox = screen.getByRole("checkbox", { + name: "Forecasted", + }); - // fireEvent.click(forecastedCheckbox); + fireEvent.click(forecastedCheckbox); - // // Since we mocked useDebouncedCallback, we expect the function to be called immediately - // // Make sure to check for both updateQueryParams and requestSubmit - // // expect(formRef.current.requestSubmit).toHaveBeenCalled(); - // expect(mockUpdateQueryParams).toHaveBeenCalledWith( - // new Set(["forecasted"]), - // "status", - // ); - // }); - - // TODO: Add more tests as needed to cover other interactions and edge cases + expect(mockUpdateQueryParams).toHaveBeenCalledWith( + combined, + "status", + undefined, + ); + }); }); diff --git a/frontend/tests/components/search/SearchPagination.test.tsx b/frontend/tests/components/search/SearchPagination.test.tsx index 330665b0d9..5d400bd5df 100644 --- a/frontend/tests/components/search/SearchPagination.test.tsx +++ b/frontend/tests/components/search/SearchPagination.test.tsx @@ -1,5 +1,16 @@ -/* eslint-disable jest/no-commented-out-tests */ -import "@testing-library/jest-dom"; +import "@testing-library/jest-dom/extend-expect"; +import { axe } from "jest-axe"; +import { fireEvent, render, screen } from "@testing-library/react"; +import React from "react"; +import SearchPagination from "src/components/search/SearchPagination"; + +const mockUpdateQueryParams = jest.fn(); + +jest.mock("src/hooks/useSearchParamUpdater", () => ({ + useSearchParamUpdater: () => ({ + updateQueryParams: mockUpdateQueryParams, + }), +})); // import SearchPagination, { // PaginationPosition, @@ -9,34 +20,21 @@ import "@testing-library/jest-dom"; // TODO (Issue #1936): Uncomment tests after React 19 upgrade describe("SearchPagination", () => { - // const mockHandlePageChange = jest.fn(); - // const page = 1; - beforeEach(() => { - jest.clearAllMocks(); - }); - it("pass test", () => { expect(1).toBe(1); }); - // it("should not have basic accessibility issues", async () => { - // const { container } = render( - // , - // ); - // const results = await axe(container, { - // rules: { - // // Disable specific rules that are known to fail due to third-party components - // list: { enabled: false }, - // "svg-img-alt": { enabled: false }, - // }, - // }); - // expect(results).toHaveNoViolations(); - // }); + it("should not have basic accessibility issues", async () => { + const { container } = render(); + + const results = await axe(container, { + rules: { + // Disable specific rules that are known to fail due to third-party components + list: { enabled: false }, + "svg-img-alt": { enabled: false }, + }, + }); + expect(results).toHaveNoViolations(); + }); // it("renders hidden input when showHiddenInput is true", () => { // render( diff --git a/frontend/tests/components/search/SearchResultsHeader.test.tsx b/frontend/tests/components/search/SearchResultsHeader.test.tsx index 957dc0db92..901ca3a944 100644 --- a/frontend/tests/components/search/SearchResultsHeader.test.tsx +++ b/frontend/tests/components/search/SearchResultsHeader.test.tsx @@ -13,14 +13,16 @@ jest.mock("src/components/search/SearchSortBy", () => { describe("SearchResultsHeader", () => { it("should not have basic accessibility issues", async () => { - const { container } = render(); + const { container } = render( + , + ); const results = await axe(container); expect(results).toHaveNoViolations(); }); it("renders correctly and displays the number of opportunities", () => { - render(); + render(); expect(screen.getByText("100 Opportunities")).toBeInTheDocument(); expect(screen.getByText("Mock SearchSortBy")).toBeInTheDocument(); diff --git a/frontend/tests/components/search/SearchSortBy.test.tsx b/frontend/tests/components/search/SearchSortBy.test.tsx index a977cc9e00..ed8a22f571 100644 --- a/frontend/tests/components/search/SearchSortBy.test.tsx +++ b/frontend/tests/components/search/SearchSortBy.test.tsx @@ -1,8 +1,8 @@ +import { axe } from "jest-axe"; import { fireEvent, render, screen } from "@testing-library/react"; - import React from "react"; import SearchSortBy from "src/components/search/SearchSortBy"; -import { axe } from "jest-axe"; +import QueryProvider from "src/app/[locale]/search/QueryProvider"; // Mock the useSearchParamUpdater hook jest.mock("src/hooks/useSearchParamUpdater", () => ({ @@ -34,20 +34,18 @@ describe("SearchSortBy", () => { }); it("updates sort option and submits the form on change", () => { - const formElement = document.createElement("form"); - const requestSubmitMock = jest.fn(); - formElement.requestSubmit = requestSubmitMock; - - render(); + const container = render( + + + , + ); - fireEvent.change(screen.getByRole("combobox"), { + fireEvent.change(container.getByRole("combobox"), { target: { value: "opportunityTitleDesc" }, }); expect( - screen.getByDisplayValue("Opportunity Title (Z to A)"), + container.getByText("Opportunity Title (Z to A)"), ).toBeInTheDocument(); - - expect(requestSubmitMock).toHaveBeenCalled(); }); }); diff --git a/frontend/tests/hooks/useSearchParamUpdater.test.ts b/frontend/tests/hooks/useSearchParamUpdater.test.ts index 2e43c05fca..b9b6830126 100644 --- a/frontend/tests/hooks/useSearchParamUpdater.test.ts +++ b/frontend/tests/hooks/useSearchParamUpdater.test.ts @@ -1,44 +1,31 @@ /* eslint-disable jest/no-commented-out-tests */ import { renderHook, waitFor } from "@testing-library/react"; - import { useSearchParamUpdater } from "src/hooks/useSearchParamUpdater"; -let mockSearchParams = new URLSearchParams(); +let mockSearchParams = new URLSearchParams(); +const routerPush = jest.fn(() => Promise.resolve(true)); jest.mock("next/navigation", () => ({ usePathname: jest.fn(() => "/test") as jest.Mock, + useRouter: () => ({ + push: routerPush, + }), useSearchParams: jest.fn( () => mockSearchParams, ) as jest.Mock, })); -const mockPushState = jest.fn(); -Object.defineProperty(window, "history", { - value: { - pushState: mockPushState, - }, - writable: true, -}); - describe("useSearchParamUpdater", () => { - beforeEach(() => { - // Reset the mock state before each test - mockPushState.mockClear(); - mockSearchParams = new URLSearchParams(); - jest.clearAllMocks(); - }); it("updates a singular param and pushes new path", async () => { const { result } = renderHook(() => useSearchParamUpdater()); - result.current.updateQueryParams("testQuery", "query", "test"); + result.current.updateQueryParams("", "query", "testQuery"); await waitFor(() => { - expect(mockPushState).toHaveBeenCalledWith( - {}, - "", - "/test?query=testQuery", - ); + expect(routerPush).toHaveBeenCalledWith("/test?query=testQuery", { + scroll: false, + }); }); }); @@ -46,19 +33,16 @@ describe("useSearchParamUpdater", () => { const { result } = renderHook(() => useSearchParamUpdater()); const statuses = new Set(["forecasted", "posted"]); - result.current.updateQueryParams(statuses, "status", "test"); + result.current.updateQueryParams(statuses, "status", "test", true); await waitFor(() => { - expect(mockPushState).toHaveBeenCalledWith( - {}, - "", - "/test?status=forecasted,posted", + expect(routerPush).toHaveBeenCalledWith( + "/test?status=forecasted,posted&query=test", + { scroll: true }, ); }); }); - // TODO: fix clear test - it("clears the status param when no statuses are selected", async () => { const { result } = renderHook(() => useSearchParamUpdater()); const statuses: Set = new Set(); @@ -66,7 +50,10 @@ describe("useSearchParamUpdater", () => { result.current.updateQueryParams(statuses, "status", "test"); await waitFor(() => { - expect(mockPushState).toHaveBeenCalledWith({}, "", "/test"); + expect(routerPush).toHaveBeenCalledWith( + "/test?query=test", + { scroll: false }, + ); }); }); });