Skip to content

Commit

Permalink
Add paginated search frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
mpbrown committed Dec 24, 2024
1 parent 4e4122d commit c2b3bde
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 122 deletions.
2 changes: 1 addition & 1 deletion frontend/src/app/Settings/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const Settings = () => {
path={"self-registration"}
element={<ManageSelfRegistrationLinksContainer />}
/>
<Route path="/:pageNumber" element={<ManageUsersContainer />} />
<Route path="users/:pageNumber" element={<ManageUsersContainer />} />
</Routes>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/app/Settings/SettingsNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ const SettingsNav = () => {
<nav className="prime-secondary-nav" aria-label="Secondary navigation">
<ul className="usa-nav__secondary-links prime-nav">
<li className="usa-nav__secondary-item">
<LinkWithQuery to={`/settings/1`} end className={classNameByActive}>
<LinkWithQuery
to={`/settings/users/1`}
end
className={classNameByActive}
>
Manage users
</LinkWithQuery>
</li>
Expand Down
80 changes: 20 additions & 60 deletions frontend/src/app/Settings/Users/ManageUsers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ let reactivateUserAndResetPassword: (obj: any) => Promise<any>;
let resetUserPassword: (obj: any) => Promise<any>;
let resetUserMfa: (obj: any) => Promise<any>;
let resendUserActivationEmail: (obj: any) => Promise<any>;
let setDebouncedQueryString = jest.fn();

type TestContainerProps = {
children: React.ReactNode;
Expand Down Expand Up @@ -365,71 +366,16 @@ describe("ManageUsers", () => {
currentPage={1}
entriesPerPage={10}
totalEntries={3}
debouncedQueryString={""}
setDebouncedQueryString={setDebouncedQueryString}
queryLoadingStatus={false}
/>
</TestContainer>
);
await waitForElementToBeRemoved(() => screen.queryByText("?, ?"));
return { user: userEvent.setup(), ...renderControls };
};

it("is searchable", async () => {
//given
const { user } = await renderAndWaitForLoad();

//when
const searchBox = screen.getByRole("searchbox", {
name: /search by name/i,
});

await user.type(searchBox, "john");

//then
await waitFor(() => {
expect(
screen.getByRole("tab", {
name: displayFullName("John", "", "Arthur"),
})
).toBeInTheDocument();
});
await waitFor(() => {
expect(
screen.queryByText(displayFullName("Bob", "", "Bobberoo"), {
exact: false,
})
).not.toBeInTheDocument();
});
});

it("displays no results message for empty filtered list", async () => {
//given
const { user } = await renderAndWaitForLoad();

//when
const searchBox = screen.getByRole("searchbox", {
name: /search by name/i,
});
await user.type(searchBox, "john wick");

//then
await waitFor(() => {
expect(
screen.queryByText(displayFullName("Jane", "", "Doe"), {
exact: false,
})
).not.toBeInTheDocument();
});

await waitFor(() => {
expect(
screen.queryByText(displayFullName("Bob", "", "Bobberoo"), {
exact: false,
})
).not.toBeInTheDocument();
});

expect(screen.getByText("No results found.")).toBeInTheDocument();
});

it("enables logged-in user's settings except deletion and roles", async () => {
const { user } = await renderAndWaitForLoad();
const nameButton = screen.getByRole("tab", {
Expand Down Expand Up @@ -779,6 +725,9 @@ describe("ManageUsers", () => {
totalEntries={0}
entriesPerPage={10}
currentPage={1}
debouncedQueryString={""}
setDebouncedQueryString={setDebouncedQueryString}
queryLoadingStatus={false}
/>
</TestContainer>
);
Expand All @@ -789,8 +738,7 @@ describe("ManageUsers", () => {

it("fails gracefully when there are no users", async () => {
await renderWithNoUsers();
const noUsers = await screen.findByText("no users", { exact: false });
expect(noUsers).toBeInTheDocument();
expect(screen.getAllByText("No results found.")).toHaveLength(2);
});

it("adds a user when zero users exist", async () => {
Expand Down Expand Up @@ -844,6 +792,9 @@ describe("ManageUsers", () => {
totalEntries={2}
entriesPerPage={10}
currentPage={1}
debouncedQueryString={""}
setDebouncedQueryString={setDebouncedQueryString}
queryLoadingStatus={false}
/>
</TestContainer>
);
Expand Down Expand Up @@ -887,6 +838,9 @@ describe("ManageUsers", () => {
totalEntries={2}
entriesPerPage={10}
currentPage={1}
debouncedQueryString={""}
setDebouncedQueryString={setDebouncedQueryString}
queryLoadingStatus={false}
/>
</TestContainer>
);
Expand Down Expand Up @@ -931,6 +885,9 @@ describe("ManageUsers", () => {
totalEntries={2}
entriesPerPage={10}
currentPage={1}
debouncedQueryString={""}
setDebouncedQueryString={setDebouncedQueryString}
queryLoadingStatus={false}
/>
</TestContainer>
);
Expand Down Expand Up @@ -1031,6 +988,9 @@ describe("ManageUsers", () => {
totalEntries={3}
entriesPerPage={10}
currentPage={1}
debouncedQueryString={""}
setDebouncedQueryString={setDebouncedQueryString}
queryLoadingStatus={false}
/>
);
render(
Expand Down
85 changes: 54 additions & 31 deletions frontend/src/app/Settings/Users/ManageUsers.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { Dispatch, useState } from "react";
import { ApolloQueryResult } from "@apollo/client";

import Button from "../../commonComponents/Button/Button";
Expand Down Expand Up @@ -43,6 +43,9 @@ interface Props {
currentPage: number;
totalEntries: number;
entriesPerPage: number;
debouncedQueryString: string;
setDebouncedQueryString: Dispatch<string>;
queryLoadingStatus: boolean;
}

export type LimitedUsers = { [id: string]: LimitedUser };
Expand Down Expand Up @@ -99,6 +102,9 @@ const ManageUsers: React.FC<Props> = ({
currentPage,
totalEntries,
entriesPerPage,
debouncedQueryString,
setDebouncedQueryString,
queryLoadingStatus,
}) => {
const [userWithPermissions, updateUserWithPermissions] =
useState<SettingsUser | null>();
Expand Down Expand Up @@ -405,22 +411,39 @@ const ManageUsers: React.FC<Props> = ({
isUpdating={isUpdating}
/>
) : null}
{!activeUser || !localUsers.length ? (
{queryLoadingStatus ? (
<div className="usa-card__body">
{!localUsers.length ? (
<p>There are no users in this organization</p>
) : (
<p>Loading user data</p>
)}
<p>Loading user data</p>
</div>
) : (
<div className="usa-card__body">
<div className="grid-row">
<UsersSideNav
activeUserId={activeUser.id || ""}
users={sortedUsers}
onChangeActiveUser={onChangeActiveUser}
/>
) : null}
<div className="usa-card__body">
<div className="grid-row">
<UsersSideNav
activeUserId={activeUser?.id || ""}
users={sortedUsers}
onChangeActiveUser={onChangeActiveUser}
debouncedQueryString={debouncedQueryString}
setDebouncedQueryString={setDebouncedQueryString}
/>
{localUsers.length <= 0 ? (
<div
className={
"display-flex flex-column flex-align-center margin-left-10 margin-top-2"
}
>
<div className="margin-bottom-105">No results found.</div>
<div>
Check for spelling errors or
<Button
className={"margin-left-1"}
id={`no-results-clear-filter-button`}
onClick={() => setDebouncedQueryString("")}
label={"Clear search filter"}
></Button>
</div>
</div>
) : null}
{activeUser ? (
<div
role="tabpanel"
aria-labelledby={"user-tab-" + user?.id}
Expand Down Expand Up @@ -501,23 +524,23 @@ const ManageUsers: React.FC<Props> = ({
/>
)}
</div>
{showInProgressModal && (
<InProgressModal
onClose={() => updateShowInProgressModal(false)}
onContinue={() => handleContinueChangeActiveUser()}
/>
)}
</div>
<div className="grid-row">
<Pagination
baseRoute={"/settings"}
totalEntries={totalEntries}
entriesPerPage={entriesPerPage}
currentPage={currentPage}
></Pagination>
</div>
) : null}
{showInProgressModal && (
<InProgressModal
onClose={() => updateShowInProgressModal(false)}
onContinue={() => handleContinueChangeActiveUser()}
/>
)}
</div>
<div className="grid-row">
<Pagination
baseRoute={"/settings"}
totalEntries={totalEntries}
entriesPerPage={entriesPerPage}
currentPage={currentPage}
></Pagination>
</div>
)}
</div>
</div>
);
};
Expand Down
Loading

0 comments on commit c2b3bde

Please sign in to comment.