From 2e653b7aa05d6cf825334d98864f7636d6e71f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 21 Oct 2026 12:02:20 +0200 Subject: [PATCH] fix(settings): name the act on the token revoke confirmation The confirm button in the API-token and MCP-token revoke dialogs read "Revoked", the status word the admin overview shows for a token that is already gone, and the phone layout's trigger carried the dialog title, question mark included. Both now say "Revoke token". --- .../__tests__/token-revoke-labels.test.ts | 52 +++++++++++++++++++ src/components/settings/api-section.tsx | 6 +-- src/components/settings/mcp-section.tsx | 4 +- 3 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/components/settings/__tests__/token-revoke-labels.test.ts diff --git a/src/components/settings/__tests__/token-revoke-labels.test.ts b/src/components/settings/__tests__/token-revoke-labels.test.ts new file mode 100644 index 000000000..880765edc --- /dev/null +++ b/src/components/settings/__tests__/token-revoke-labels.test.ts @@ -0,0 +1,52 @@ +/** + * The token revoke confirmation names the act. + * + * Both token lists (API tokens and MCP connections) labelled the dialog's + * confirm button with `settings.tokenRevoked` — "Revoked", the status word the + * admin overview shows for a token that is already gone — so the button that + * performs the revoke read as if it had happened. The phone layout's trigger + * carried the dialog title instead, question mark included. + * + * Read from the source because the dialog content only mounts while open, and + * an SSR render never sees it. What is pinned: every `AlertDialogAction` that + * calls `handleRevoke` is labelled with the action key, and no trigger or + * action on these two surfaces uses the status word. + */ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +const FILES = [ + "src/components/settings/api-section.tsx", + "src/components/settings/mcp-section.tsx", +]; + +function read(rel: string): string { + return readFileSync(join(process.cwd(), rel), "utf8"); +} + +/** The body of every `…` block. */ +function actions(source: string): string[] { + return [ + ...source.matchAll(//g), + ].map((m) => m[0]); +} + +describe("token revoke labels", () => { + for (const rel of FILES) { + it(`${rel}: the revoke confirm says "Revoke token"`, () => { + const revokes = actions(read(rel)).filter((a) => + a.includes("handleRevoke(tok.id)"), + ); + expect(revokes.length).toBeGreaterThan(0); + for (const action of revokes) { + expect(action).toContain('t("settings.tokenRevokeAction")'); + } + }); + + it(`${rel}: never labels a control with the status word`, () => { + expect(read(rel)).not.toContain('t("settings.tokenRevoked")'); + }); + } +}); diff --git a/src/components/settings/api-section.tsx b/src/components/settings/api-section.tsx index fa0edc26b..267fd27d3 100644 --- a/src/components/settings/api-section.tsx +++ b/src/components/settings/api-section.tsx @@ -619,7 +619,7 @@ function ApiTokensCard() { variant="destructive" onClick={() => handleRevoke(tok.id)} > - {t("settings.tokenRevoked")} + {t("settings.tokenRevokeAction")} @@ -698,7 +698,7 @@ function ApiTokensCard() { className="min-h-11 w-full" > - {t("settings.tokenRevoke")} + {t("settings.tokenRevokeAction")} @@ -718,7 +718,7 @@ function ApiTokensCard() { variant="destructive" onClick={() => handleRevoke(tok.id)} > - {t("settings.tokenRevoked")} + {t("settings.tokenRevokeAction")} diff --git a/src/components/settings/mcp-section.tsx b/src/components/settings/mcp-section.tsx index bc21fcb4f..6b448a5d6 100644 --- a/src/components/settings/mcp-section.tsx +++ b/src/components/settings/mcp-section.tsx @@ -516,7 +516,7 @@ function McpTokensCard() { className="min-h-11 w-full" > - {t("settings.tokenRevoke")} + {t("settings.tokenRevokeAction")} @@ -536,7 +536,7 @@ function McpTokensCard() { variant="destructive" onClick={() => handleRevoke(tok.id)} > - {t("settings.tokenRevoked")} + {t("settings.tokenRevokeAction")}