Skip to content

feat: have user type name of thing to delete for extra safety #4080

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

Merged
merged 12 commits into from
Sep 20, 2022
Prev Previous commit
Next Next commit
Add and update tests
  • Loading branch information
presleyp committed Sep 15, 2022
commit b8a6c53df62ab68f090fbc74e5a129dd84d22154
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export default {
defaultValue: "foo",
},
name: {
defaultValue: "MyFoo"
defaultValue: "MyFoo",
},
info: {
defaultValue: "Here's some info about the foo so you know you're deleting the right one."
}
defaultValue: "Here's some info about the foo so you know you're deleting the right one.",
},
},
} as ComponentMeta<typeof DeleteDialog>

Expand Down
57 changes: 57 additions & 0 deletions site/src/components/Dialogs/DeleteDialog/DeleteDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import i18next from "i18next"
import { render } from "testHelpers/renderHelpers"
import { DeleteDialog } from "./DeleteDialog"

describe("DeleteDialog", () => {
it("disables confirm button when the text field is empty", () => {
render(
<DeleteDialog
isOpen
onConfirm={jest.fn()}
onCancel={jest.fn()}
entity="template"
name="MyTemplate"
/>,
)
const confirmButton = screen.getByRole("button", { name: "Delete" })
expect(confirmButton).toBeDisabled()
})

it("disables confirm button when the text field is filled incorrectly", async () => {
const { t } = i18next
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

render(
<DeleteDialog
isOpen
onConfirm={jest.fn()}
onCancel={jest.fn()}
entity="template"
name="MyTemplate"
/>,
)
const labelText = t("deleteDialog.confirmLabel", { ns: "common", entity: "template" })
const textField = screen.getByLabelText(labelText)
await userEvent.type(textField, "MyTemplateWrong")
const confirmButton = screen.getByRole("button", { name: "Delete" })
expect(confirmButton).toBeDisabled()
})

it("enables confirm button when the text field is filled correctly", async () => {
const { t } = i18next
render(
<DeleteDialog
isOpen
onConfirm={jest.fn()}
onCancel={jest.fn()}
entity="template"
name="MyTemplate"
/>,
)
const labelText = t("deleteDialog.confirmLabel", { ns: "common", entity: "template" })
const textField = screen.getByLabelText(labelText)
await userEvent.type(textField, "MyTemplate")
const confirmButton = screen.getByRole("button", { name: "Delete" })
expect(confirmButton).not.toBeDisabled()
})
})
9 changes: 8 additions & 1 deletion site/src/components/Dialogs/DeleteDialog/DeleteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ export const DeleteDialog: React.FC<React.PropsWithChildren<DeleteDialogProps>>
</Maybe>
<Typography>{t("deleteDialog.confirm", { entity })}</Typography>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love how we're not passing whole strings in so that the language will stay consistent.

<Stack spacing={1}>
<TextField placeholder={name} value={nameValue} onChange={handleChange} />
<TextField
name="confirmation"
id="confirmation"
placeholder={name}
value={nameValue}
onChange={handleChange}
label={t("deleteDialog.confirmLabel", { entity })}
/>
<Maybe condition={nameValue.length > 0 && !confirmed}>
<FormHelperText error>{t("deleteDialog.incorrectName", { entity })}</FormHelperText>
</Maybe>
Expand Down
1 change: 1 addition & 0 deletions site/src/i18n/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"title": "Delete {{entity}}",
"intro": "Deleting this {{entity}} is irreversible!",
"confirm": "Are you sure you want to proceed? Type the name of this {{entity}} below to confirm.",
"confirmLabel": "Name of {{entity}} to delete",
"incorrectName": "Incorrect {{entity}} name."
}
}
13 changes: 11 additions & 2 deletions site/src/pages/UsersPage/UsersPage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { fireEvent, screen, waitFor, within } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { i18n } from "i18n"
import { rest } from "msw"
import { Language as usersXServiceLanguage } from "xServices/users/usersXService"
import * as API from "../../api/api"
Expand All @@ -20,6 +22,8 @@ import { permissionsToCheck } from "../../xServices/auth/authXService"
import { Language as UsersPageLanguage, UsersPage } from "./UsersPage"
import { Language as UsersViewLanguage } from "./UsersPageView"

const { t } = i18n

const suspendUser = async (setupActionSpies: () => void) => {
// Get the first user in the table
const users = await screen.findAllByText(/.*@coder.com/)
Expand Down Expand Up @@ -73,14 +77,19 @@ const deleteUser = async (setupActionSpies: () => void) => {
// Check if the confirm message is displayed
const confirmDialog = await screen.findByRole("dialog")
expect(confirmDialog).toHaveTextContent(
`${UsersPageLanguage.deleteDialogMessagePrefix} ${MockUser.username}?`,
t("deleteDialog.confirm", { ns: "common", entity: "user" }),
)

// Confirm with text input
const labelText = t("deleteDialog.confirmLabel", { ns: "common", entity: "user" })
const textField = screen.getByLabelText(labelText)
await userEvent.type(textField, MockUser.username)

// Setup spies to check the actions after
setupActionSpies()

// Click on the "Confirm" button
const confirmButton = within(confirmDialog).getByText(UsersPageLanguage.deleteDialogAction)
const confirmButton = screen.getByRole("button", { name: "Delete" })
fireEvent.click(confirmButton)
}

Expand Down
10 changes: 6 additions & 4 deletions site/src/pages/WorkspacePage/WorkspacePage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { fireEvent, screen, waitFor, within } from "@testing-library/react"
import { fireEvent, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import i18next from "i18next"
import { rest } from "msw"
import * as api from "../../api/api"
Expand Down Expand Up @@ -103,9 +104,10 @@ describe("WorkspacePage", () => {
const button = await screen.findByText(Language.delete)
fireEvent.click(button)

const confirmDialog = await screen.findByRole("dialog")
const confirmButton = within(confirmDialog).getByText("Delete")

const labelText = t("deleteDialog.confirmLabel", { ns: "common", entity: "workspace" })
const textField = screen.getByLabelText(labelText)
await userEvent.type(textField, MockWorkspace.name)
const confirmButton = screen.getByRole("button", { name: "Delete" })
fireEvent.click(confirmButton)
expect(deleteWorkspaceMock).toBeCalled()
})
Expand Down