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 26, 2024
1 parent ea519b2 commit b2174fb
Show file tree
Hide file tree
Showing 9 changed files with 180 additions and 91 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
124 changes: 124 additions & 0 deletions frontend/src/app/Settings/Users/ManageUsersContainer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
ORG_ADMIN_REACTIVATE_COPY,
SITE_ADMIN_REACTIVATE_COPY,
} from "../../commonComponents/UserDetails/ReactivateUserModal";
import { displayFullName } from "../../utils";

import ManageUsersContainer from "./ManageUsersContainer";

Expand Down Expand Up @@ -105,6 +106,7 @@ describe("ManageUsersContainer", () => {
query: GetUsersAndStatusPageDocument,
variables: {
pageNumber: 0,
searchQuery: "",
},
},
result: {
Expand Down Expand Up @@ -149,6 +151,52 @@ describe("ManageUsersContainer", () => {
},
},
},
{
request: {
operationName: "GetUsersAndStatusPage",
query: GetUsersAndStatusPageDocument,
variables: {
pageNumber: 0,
searchQuery: "bob",
},
},
result: {
data: {
usersWithStatusPage: {
totalElements: 1,
content: [
{
id: "1029653e-24d9-428e-83b0-468319948902",
firstName: "Bob",
middleName: null,
lastName: "Bobberoo",
email: "[email protected]",
status: "ACTIVE",
__typename: "ApiUserWithStatus",
},
],
},
},
},
},
{
request: {
operationName: "GetUsersAndStatusPage",
query: GetUsersAndStatusPageDocument,
variables: {
pageNumber: 0,
searchQuery: "john wick",
},
},
result: {
data: {
usersWithStatusPage: {
totalElements: 0,
content: [],
},
},
},
},
];

const supendedUserMocks: MockedResponse[] = [
Expand All @@ -158,6 +206,7 @@ describe("ManageUsersContainer", () => {
query: GetUsersAndStatusPageDocument,
variables: {
pageNumber: 0,
searchQuery: "",
},
},
result: {
Expand Down Expand Up @@ -273,6 +322,81 @@ describe("ManageUsersContainer", () => {
await screen.findByText(/Error: Users not found/i);
});

it("is searchable", async () => {
//given
const { user } = renderComponentWithMocks(mocks, store);
await waitForElementToBeRemoved(screen.queryByText("Loading..."));
await waitFor(() =>
expect(
screen.queryByRole("heading", {
level: 2,
description: /barnes, ben billy/i,
})
)
);

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

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

it("displays no results message for empty filtered list", async () => {
//given
const { user } = renderComponentWithMocks(mocks, store);
await waitForElementToBeRemoved(screen.queryByText("Loading..."));
await waitFor(() =>
expect(
screen.queryByRole("heading", {
level: 2,
description: /barnes, ben billy/i,
})
)
);

//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.getAllByText("No results found.")).toHaveLength(2);
});

it("when user is an org admin , modal copy reflects the reactivate only flow", async () => {
const { user } = renderComponentWithMocks(supendedUserMocks, store);

Expand Down
11 changes: 11 additions & 0 deletions frontend/src/app/Settings/Users/ManageUsersContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
useGetUsersAndStatusPageQuery,
} from "../../../generated/graphql";
import { useDocumentTitle } from "../../utils/hooks";
import { useDebounce } from "../../testQueue/addToQueue/useDebounce";
import { SEARCH_DEBOUNCE_TIME } from "../../testQueue/constants";

import ManageUsers from "./ManageUsers";

Expand Down Expand Up @@ -93,6 +95,11 @@ const ManageUsersContainer = () => {
const [resetMfa] = useResetUserMfaMutation();
const [resendUserActivationEmail] = useResendActivationEmailMutation();

const [queryString, debouncedQueryString, setDebouncedQueryString] =
useDebounce("", {
debounceTime: SEARCH_DEBOUNCE_TIME,
});

const { pageNumber } = useParams();
const currentPage = pageNumber ? +pageNumber : 1;
const entriesPerPage = 10;
Expand All @@ -106,6 +113,7 @@ const ManageUsersContainer = () => {
fetchPolicy: "no-cache",
variables: {
pageNumber: currentPage - 1,
searchQuery: queryString,
},
});

Expand Down Expand Up @@ -139,6 +147,9 @@ const ManageUsersContainer = () => {
currentPage={currentPage}
totalEntries={data.usersWithStatusPage.totalElements}
entriesPerPage={entriesPerPage}
debouncedQueryString={debouncedQueryString}
setDebouncedQueryString={setDebouncedQueryString}
queryLoadingStatus={loading}
/>
);
};
Expand Down
Loading

0 comments on commit b2174fb

Please sign in to comment.