Place account deletion in its own danger row #82

Merged
an.di merged 2 commits from adilallo/fix/CR-207-delete-account-danger into main 2026-09-28 21:18:06 +00:00
2 changed files with 170 additions and 11 deletions
Showing only changes of commit 72c51adbb3 - Show all commits

No files matched your search

@@ -237,17 +237,8 @@ export function ProfilePageView({
leadingIcon: "mail", leadingIcon: "mail",
showDescription: false, showDescription: false,
}, },
{
id: "delete-account",
title: t("deleteAccount"),
description: "",
onClick: onOpenDeleteAccount,
leadingIcon: "warning",
variant: "danger",
showDescription: false,
},
]; ];
}, [t, onSignOut, onOpenDeleteAccount, onOpenEmailChange, onStartNewCustomRule]); }, [t, onSignOut, onOpenEmailChange, onStartNewCustomRule]);
const ruleCardShellClass = const ruleCardShellClass =
"w-full !max-w-full cursor-default !gap-3 !rounded-[12px] shadow-[0_0_48px_rgba(0,0,0,0.1)] lg:!rounded-[24px] lg:shadow-[0_0_24px_rgba(0,0,0,0.1)]"; "w-full !max-w-full cursor-default !gap-3 !rounded-[12px] shadow-[0_0_48px_rgba(0,0,0,0.1)] lg:!rounded-[24px] lg:shadow-[0_0_24px_rgba(0,0,0,0.1)]";
@@ -408,6 +399,31 @@ export function ProfilePageView({
</nav> </nav>
</section> </section>
</div> </div>
<section
className="flex w-full flex-col items-start gap-3 rounded-[12px] border border-[var(--color-border-default-negative-primary)] bg-[var(--color-surface-default-negative-secondary)] px-4 py-4 lg:px-6 lg:py-6"
aria-labelledby="profile-delete-heading"
>
<h2
id="profile-delete-heading"
className="text-small-label text-[var(--color-content-default-negative-primary)]"
>
{t("deleteAccount")}
</h2>
<p className="max-w-[640px] text-small-paragraph text-[var(--color-content-default-secondary)]">
{t("deleteAccountIntro")}
</p>
<Button
type="button"
size="small"
buttonType="danger"
palette="default"
className="self-start"
onClick={onOpenDeleteAccount}
>
{t("deleteAccountConfirm")}
</Button>
</section>
</div> </div>
</div> </div>
@@ -502,7 +518,7 @@ export function ProfilePageView({
<Button <Button
type="button" type="button"
size="medium" size="medium"
buttonType="filled" buttonType="danger"
palette="default" palette="default"
onClick={onConfirmDeleteAccount} onClick={onConfirmDeleteAccount}
disabled={accountDeleteBusy} disabled={accountDeleteBusy}
+143
View File
@@ -0,0 +1,143 @@
import { afterEach, describe, expect, test, vi } from "vitest";
import userEvent from "@testing-library/user-event";
import { within } from "@testing-library/react";
import {
cleanup,
renderWithProviders as render,
screen,
} from "../utils/test-utils";
import {
ProfilePageView,
type ProfilePageViewProps,
} from "../../app/(app)/profile/_components/ProfilePage.view";
import messages from "../../messages/en/index";
const copy = messages.pages.profile;
afterEach(() => {
cleanup();
});
function viewProps(
overrides: Partial<ProfilePageViewProps> = {},
): ProfilePageViewProps {
return {
userEmail: "dani@example.com",
ruleCardSize: "M",
profileLgUp: true,
profileListSize: "l",
rules: [],
rulesError: false,
draft: null,
showDraftCard: false,
ruleDeleteOpen: false,
ruleDeleteBusy: false,
draftDeleteOpen: false,
draftDeleteBusy: false,
accountDeleteOpen: false,
accountDeleteBusy: false,
actionError: null,
profileSuccessMessage: null,
emailChangeOpen: false,
emailChangeValue: "",
onEmailChangeValueChange: vi.fn(),
emailChangeBusy: false,
emailChangeRequestSent: false,
emailChangeModalError: null,
onDismissEmailChangeModalError: vi.fn(),
onOpenEmailChange: vi.fn(),
onCloseEmailChange: vi.fn(),
onSubmitEmailChange: vi.fn(),
onSignOut: vi.fn(),
onDeleteRule: vi.fn(),
onCloseDeleteRule: vi.fn(),
onConfirmDeleteRule: vi.fn(),
onDuplicateRule: vi.fn(),
onContinueDraft: vi.fn(),
onDeleteDraft: vi.fn(),
onCloseDeleteDraft: vi.fn(),
onConfirmDeleteDraft: vi.fn(),
onOpenDeleteAccount: vi.fn(),
onCloseDeleteAccount: vi.fn(),
onConfirmDeleteAccount: vi.fn(),
onDismissProfileSuccess: vi.fn(),
onDismissActionError: vi.fn(),
onDismissRulesError: vi.fn(),
onStartNewCustomRule: vi.fn(),
...overrides,
};
}
describe("ProfilePageView", () => {
test("keeps account deletion out of the routine options list", () => {
render(<ProfilePageView {...viewProps()} />);
const options = screen.getByRole("navigation", {
name: copy.yourOptionsHeading,
});
const createRule = within(options).getByRole("button", {
name: copy.optionCreateCustom,
});
expect(createRule.closest("ul")).not.toBeNull();
expect(
within(options).queryByRole("button", { name: copy.deleteAccountConfirm }),
).not.toBeInTheDocument();
expect(
within(options).queryByRole("heading", { name: copy.deleteAccount }),
).not.toBeInTheDocument();
const danger = screen.getByRole("region", { name: copy.deleteAccount });
expect(danger).toHaveTextContent(copy.deleteAccountIntro);
const deleteAccount = within(danger).getByRole("button", {
name: copy.deleteAccountConfirm,
});
expect(deleteAccount.closest("ul")).toBeNull();
expect(deleteAccount.className).toContain(
"border-[var(--color-border-default-negative-primary)]",
);
expect(deleteAccount.className).not.toContain(
"bg-[var(--color-surface-inverse-primary)]",
);
});
test("asks for confirmation before deleting the account", async () => {
const user = userEvent.setup();
const onOpenDeleteAccount = vi.fn();
const onConfirmDeleteAccount = vi.fn();
const { rerender } = render(
<ProfilePageView {...viewProps({ onOpenDeleteAccount })} />,
);
await user.click(
screen.getByRole("button", { name: copy.deleteAccountConfirm }),
);
expect(onOpenDeleteAccount).toHaveBeenCalledTimes(1);
rerender(
<ProfilePageView
{...viewProps({
onOpenDeleteAccount,
onConfirmDeleteAccount,
accountDeleteOpen: true,
})}
/>,
);
const dialog = screen.getByRole("dialog", {
name: copy.deleteAccountModalTitle,
});
expect(dialog).toHaveTextContent(copy.deleteAccountModalBody);
const confirm = within(dialog).getByRole("button", {
name: copy.deleteAccountConfirm,
});
expect(confirm.className).toContain(
"border-[var(--color-border-default-negative-primary)]",
);
expect(confirm.className).not.toContain(
"bg-[var(--color-surface-inverse-primary)]",
);
await user.click(confirm);
expect(onConfirmDeleteAccount).toHaveBeenCalledTimes(1);
});
});