-
Notifications
You must be signed in to change notification settings - Fork 937
feat: Add suspend user action #1275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c2f6e1c
1f113e8
fa5eae5
ab5f160
0067770
286fde6
5a43eac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,93 @@ | ||
import { screen } from "@testing-library/react" | ||
import { fireEvent, screen, waitFor, within } from "@testing-library/react" | ||
import React from "react" | ||
import { render } from "../../testHelpers" | ||
import { UsersPage } from "./UsersPage" | ||
import * as API from "../../api" | ||
import { GlobalSnackbar } from "../../components/GlobalSnackbar/GlobalSnackbar" | ||
import { Language as UsersTableLanguage } from "../../components/UsersTable/UsersTable" | ||
import { MockUser, MockUser2, render } from "../../testHelpers" | ||
import { Language as usersXServiceLanguage } from "../../xServices/users/usersXService" | ||
import { Language as UsersPageLanguage, UsersPage } from "./UsersPage" | ||
|
||
const suspendUser = async (setupActionSpies: () => void) => { | ||
// Get the first user in the table | ||
const users = await screen.findAllByText(/.*@coder.com/) | ||
const firstUserRow = users[0].closest("tr") | ||
if (!firstUserRow) { | ||
throw new Error("Error on get the first user row") | ||
} | ||
|
||
// Click on the "more" button to display the "Suspend" option | ||
const moreButton = within(firstUserRow).getByLabelText("more") | ||
fireEvent.click(moreButton) | ||
const menu = screen.getByRole("menu") | ||
const suspendButton = within(menu).getByText(UsersTableLanguage.suspendMenuItem) | ||
fireEvent.click(suspendButton) | ||
|
||
// Check if the confirm message is displayed | ||
const confirmDialog = screen.getByRole("dialog") | ||
expect(confirmDialog).toHaveTextContent(`${UsersPageLanguage.suspendDialogMessagePrefix} ${MockUser.username}?`) | ||
|
||
// Setup spies to check the actions after | ||
setupActionSpies() | ||
|
||
// Click on the "Confirm" button | ||
const confirmButton = within(confirmDialog).getByText(UsersPageLanguage.suspendDialogAction) | ||
fireEvent.click(confirmButton) | ||
} | ||
|
||
describe("Users Page", () => { | ||
it("shows users", async () => { | ||
render(<UsersPage />) | ||
const users = await screen.findAllByText(/.*@coder.com/) | ||
expect(users.length).toEqual(2) | ||
}) | ||
|
||
describe("suspend user", () => { | ||
describe("when it is success", () => { | ||
it("shows a success message and refresh the page", async () => { | ||
render( | ||
<> | ||
<UsersPage /> | ||
<GlobalSnackbar /> | ||
</>, | ||
) | ||
|
||
await suspendUser(() => { | ||
jest.spyOn(API, "suspendUser").mockResolvedValueOnce(MockUser) | ||
jest.spyOn(API, "getUsers").mockImplementationOnce(() => Promise.resolve([MockUser, MockUser2])) | ||
}) | ||
|
||
// Check if the success message is displayed | ||
await screen.findByText(usersXServiceLanguage.suspendUserSuccess) | ||
|
||
// Check if the API was called correctly | ||
expect(API.suspendUser).toBeCalledTimes(1) | ||
expect(API.suspendUser).toBeCalledWith(MockUser.id) | ||
|
||
// Check if the users list was reload | ||
await waitFor(() => expect(API.getUsers).toBeCalledTimes(1)) | ||
}) | ||
}) | ||
|
||
describe("when it fails", () => { | ||
it("shows an error message", async () => { | ||
render( | ||
<> | ||
<UsersPage /> | ||
<GlobalSnackbar /> | ||
</>, | ||
) | ||
|
||
await suspendUser(() => { | ||
jest.spyOn(API, "suspendUser").mockRejectedValueOnce({}) | ||
}) | ||
|
||
// Check if the success message is displayed | ||
await screen.findByText(usersXServiceLanguage.suspendUserError) | ||
|
||
// Check if the API was called correctly | ||
expect(API.suspendUser).toBeCalledTimes(1) | ||
expect(API.suspendUser).toBeCalledWith(MockUser.id) | ||
}) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,23 @@ | ||
import { useActor } from "@xstate/react" | ||
import React, { useContext, useEffect } from "react" | ||
import { useNavigate } from "react-router" | ||
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary" | ||
import { ConfirmDialog } from "../../components/ConfirmDialog/ConfirmDialog" | ||
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader" | ||
import { XServiceContext } from "../../xServices/StateContext" | ||
import { UsersPageView } from "./UsersPageView" | ||
|
||
export const Language = { | ||
suspendDialogTitle: "Suspend user", | ||
suspendDialogAction: "Suspend", | ||
suspendDialogMessagePrefix: "Do you want to suspend the user", | ||
} | ||
|
||
export const UsersPage: React.FC = () => { | ||
const xServices = useContext(XServiceContext) | ||
const [usersState, usersSend] = useActor(xServices.usersXService) | ||
const { users, getUsersError } = usersState.context | ||
const { users, getUsersError, userIdToSuspend } = usersState.context | ||
const navigate = useNavigate() | ||
const userToBeSuspended = users?.find((u) => u.id === userIdToSuspend) | ||
|
||
/** | ||
* Fetch users on component mount | ||
|
@@ -19,20 +26,42 @@ export const UsersPage: React.FC = () => { | |
usersSend("GET_USERS") | ||
}, [usersSend]) | ||
|
||
if (usersState.matches("error")) { | ||
return <ErrorSummary error={getUsersError} /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I moved this to be inside of UsersPage, so the getUsersError does not block the UI(not showing the table) for the other non-blocking errors like the suspendUserError. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My idea for that was to not go to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I didn't understand. Without removing this statement, any error will remove the user table from the screen and show the error summary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm suggesting that every error gets assigned to context so that we can display error messages, but that only blocking errors put the page in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh I see it, good to know! |
||
} | ||
|
||
if (!users) { | ||
return <FullScreenLoader /> | ||
} else { | ||
return ( | ||
<UsersPageView | ||
users={users} | ||
openUserCreationDialog={() => { | ||
navigate("/users/create") | ||
}} | ||
/> | ||
<> | ||
<UsersPageView | ||
users={users} | ||
openUserCreationDialog={() => { | ||
navigate("/users/create") | ||
}} | ||
onSuspendUser={(user) => { | ||
usersSend({ type: "SUSPEND_USER", userId: user.id }) | ||
}} | ||
error={getUsersError} | ||
/> | ||
|
||
<ConfirmDialog | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is so clean, love it |
||
type="delete" | ||
hideCancel={false} | ||
open={usersState.matches("confirmUserSuspension")} | ||
confirmLoading={usersState.matches("suspendingUser")} | ||
title={Language.suspendDialogTitle} | ||
confirmText={Language.suspendDialogAction} | ||
onConfirm={() => { | ||
usersSend("CONFIRM_USER_SUSPENSION") | ||
}} | ||
onClose={() => { | ||
usersSend("CANCEL_USER_SUSPENSION") | ||
}} | ||
description={ | ||
<> | ||
{Language.suspendDialogMessagePrefix} <strong>{userToBeSuspended?.username}</strong>? | ||
</> | ||
} | ||
/> | ||
</> | ||
) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds some helpful test matchers!