diff --git a/src/components/settings/security-section/__tests__/totp-card.test.tsx b/src/components/settings/security-section/__tests__/totp-card.test.tsx
index 9b778c031..8e82079c6 100644
--- a/src/components/settings/security-section/__tests__/totp-card.test.tsx
+++ b/src/components/settings/security-section/__tests__/totp-card.test.tsx
@@ -8,6 +8,26 @@
* Project convention is SSR-only tests (no `@testing-library/react`); the
* dialog's open-state behaviour is Radix's own, so we assert the source wires
* the trigger + consequence copy rather than driving a portal at runtime.
+ *
+ * ## The refused mutation
+ *
+ * A refusal used to leave the confirmation up: the confirming click
+ * `preventDefault()`s Radix's close so the request can stay in flight behind a
+ * disabled button, and nothing took the dialog down again. On success that was
+ * invisible — the fresh-codes panel replaces the branch the dialog lives in,
+ * so it unmounted anyway — but a refused rotation (or a mistyped code at
+ * disable) left an overlay covering the one place the card explains itself.
+ *
+ * Two halves are pinned below: the sentence a step-up refusal puts on the
+ * card, called directly; and the wiring that takes the dialog down when the
+ * request settles, read off the source. The second half is structural because
+ * the DOM is out of reach here — what a person actually sees, a closed dialog
+ * over a visible message, is asserted in `e2e/mfa-step-up-journey.spec.ts`
+ * against a real browser.
+ *
+ * Mutation check: drop either `onSettled` and the wiring case goes red; drop
+ * an `open=` binding and the controlled case goes red; return `err.message`
+ * from `describeError`'s step-up arm and the sentence case goes red.
*/
import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
@@ -15,8 +35,12 @@ import { resolve } from "node:path";
import { renderToStaticMarkup } from "react-dom/server";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { ApiError } from "@/lib/api/api-fetch";
import { I18nProvider } from "@/lib/i18n/context";
-import { TotpCard } from "../totp-card";
+import en from "@/../messages/en.json";
+import { describeError, TotpCard } from "../totp-card";
+
+const STEP_UP_SENTENCE = en.settings.security.stepUpRequired;
function render(node: React.ReactNode) {
const client = new QueryClient({
@@ -29,6 +53,10 @@ function render(node: React.ReactNode) {
);
}
+function source(): string {
+ return readFileSync(resolve(__dirname, "../totp-card.tsx"), "utf8");
+}
+
describe("