From d3ac1a600cb85cd930721272e5e5bb6d3d4b68ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rouzbeh=E2=80=A0?=
<78313022+rqzbeh@users.noreply.github.com>
Date: Sun, 23 Aug 2026 02:43:10 +0330
Subject: [PATCH] refactor(dashboard): mirror check button disable state in
AddApiKeyModal Enter handler (#10995) (#11156)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Cherry-picked onto the current tip (authorship preserved), generated-count noise stripped. Pre-merge: file-size baseline rebaselined 1080→1082 with dated annotation (the +2 lines are the Enter-handler isCheckDisabled mirror — owner-requested #11056 polish; rest is Prettier reflow). Gate green; vitest add-api-key-modal-enter-key 2/2 (jsdom render test). Thank you @rqzbeh!
---
config/quality/file-size-baseline.json | 3 +-
.../[id]/components/modals/AddApiKeyModal.tsx | 80 +++++++-------
.../ui/add-api-key-modal-enter-key.test.ts | 26 -----
.../ui/add-api-key-modal-enter-key.test.tsx | 104 ++++++++++++++++++
4 files changed, 147 insertions(+), 66 deletions(-)
delete mode 100644 tests/unit/ui/add-api-key-modal-enter-key.test.ts
create mode 100644 tests/unit/ui/add-api-key-modal-enter-key.test.tsx
diff --git a/config/quality/file-size-baseline.json b/config/quality/file-size-baseline.json
index 536318240a..b7f40cebfa 100644
--- a/config/quality/file-size-baseline.json
+++ b/config/quality/file-size-baseline.json
@@ -443,7 +443,8 @@
"src/shared/components/ModelSelectModal.tsx": 1138,
"src/shared/constants/providers/apikey/gateways.ts": 1250
},
- "src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1080,
+ "src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx": 1082,
+ "_rebaseline_2026_08_22_11156_enter_check_disabled": "PR #11156 (rqzbeh) own growth: AddApiKeyModal.tsx 1080->1082 (+2, Enter keydown handler now mirrors the isCheckDisabled condition — owner-requested post-merge polish from #11056; the rest of the diff is Prettier reflow). Covered by tests/unit/ui/add-api-key-modal-enter-key.test.tsx (jsdom render test, Enter dispatch assertions).",
"src/app/(dashboard)/dashboard/providers/[id]/hooks/useProviderConnections.ts": 1051,
"src/shared/components/ModelSelectModal.tsx": 1138,
"src/shared/constants/providers/apikey/gateways.ts": 1298,
diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx
index 75917aec57..9a2bdc0038 100644
--- a/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx
+++ b/src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx
@@ -757,46 +757,48 @@ export default function AddApiKeyModal({
onImport={(apiKey) => setFormData({ ...formData, apiKey })}
/>
)}
- {!isNoAuthWebSessionCredential && (
-
-
setFormData({ ...formData, apiKey: e.target.value })}
- onKeyDown={(e) => {
- if (e.key === "Enter" && !validating && !saving) {
- e.preventDefault();
- handleValidate();
- }
- }}
- className="flex-1"
- placeholder={apiCredentialPlaceholder}
- hint={apiCredentialHint}
- autoComplete="off"
- spellCheck={false}
- autoCapitalize="off"
- />
-
-
+ {!isNoAuthWebSessionCredential && (() => {
+ const isCheckDisabled =
+ (!isCompatible && !apiKeyOptional && !formData.apiKey) ||
+ (isGooglePse && !formData.cx.trim()) ||
+ validating ||
+ saving;
+ return (
+
+
setFormData({ ...formData, apiKey: e.target.value })}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && !isCheckDisabled) {
+ e.preventDefault();
+ handleValidate();
+ }
+ }}
+ className="flex-1"
+ placeholder={apiCredentialPlaceholder}
+ hint={apiCredentialHint}
+ autoComplete="off"
+ spellCheck={false}
+ autoCapitalize="off"
+ />
+
+
+
-
- )}
+ );
+ })()}
{isChatGptWebCodex && (
diff --git a/tests/unit/ui/add-api-key-modal-enter-key.test.ts b/tests/unit/ui/add-api-key-modal-enter-key.test.ts
deleted file mode 100644
index 2532eb297e..0000000000
--- a/tests/unit/ui/add-api-key-modal-enter-key.test.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { describe, it } from "node:test";
-import assert from "node:assert/strict";
-import fs from "node:fs";
-import path from "node:path";
-
-describe("AddApiKeyModal Enter key submit (#10995)", () => {
- it("AddApiKeyModal attaches onKeyDown Enter handler to the API Key input", () => {
- const modalPath = path.resolve(
- process.cwd(),
- "src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal.tsx"
- );
- const content = fs.readFileSync(modalPath, "utf8");
- assert.ok(
- content.includes("onKeyDown"),
- "AddApiKeyModal must contain onKeyDown event handler for Enter key validation"
- );
- assert.ok(
- content.includes('e.key === "Enter"'),
- "onKeyDown handler must check for Enter key press"
- );
- assert.ok(
- content.includes("handleValidate()"),
- "Enter key press must invoke handleValidate()"
- );
- });
-});
diff --git a/tests/unit/ui/add-api-key-modal-enter-key.test.tsx b/tests/unit/ui/add-api-key-modal-enter-key.test.tsx
new file mode 100644
index 0000000000..1c28a13230
--- /dev/null
+++ b/tests/unit/ui/add-api-key-modal-enter-key.test.tsx
@@ -0,0 +1,104 @@
+// @vitest-environment jsdom
+//
+// #10995 — Enter key in AddApiKeyModal triggers key validation without requiring a mouse click on Check.
+import React, { act } from "react";
+import { createRoot } from "react-dom/client";
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
+
+vi.mock("next-intl", () => ({
+ useTranslations: () => (key: string) => key,
+}));
+
+const { default: AddApiKeyModal } =
+ await import("../../../src/app/(dashboard)/dashboard/providers/[id]/components/modals/AddApiKeyModal");
+
+const containers: Array<{ root: ReturnType
; el: HTMLDivElement }> = [];
+
+function render(props: Record) {
+ const el = document.createElement("div");
+ document.body.appendChild(el);
+ const root = createRoot(el);
+ act(() => {
+ root.render(
+ undefined}
+ onClose={() => {}}
+ {...(props as any)}
+ />
+ );
+ });
+ containers.push({ root, el });
+ return el;
+}
+
+function setInputValue(input: HTMLInputElement, value: string) {
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")!.set!;
+ act(() => {
+ setter.call(input, value);
+ input.dispatchEvent(new Event("input", { bubbles: true }));
+ });
+}
+
+function dispatchKeyDown(element: HTMLElement, key: string) {
+ act(() => {
+ element.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true, cancelable: true }));
+ });
+}
+
+describe("AddApiKeyModal Enter key submit (#10995)", () => {
+ let originalFetch: typeof global.fetch;
+
+ beforeEach(() => {
+ originalFetch = global.fetch;
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ for (const { root, el } of containers) {
+ act(() => root.unmount());
+ el.remove();
+ }
+ containers.length = 0;
+ });
+
+ it("does not trigger validation on Enter when input is empty", () => {
+ const fetchMock = vi.fn().mockResolvedValue({
+ ok: true,
+ json: async () => ({ valid: true }),
+ });
+ global.fetch = fetchMock as any;
+
+ const el = render({});
+ const input = el.querySelector('input[type="password"]');
+ expect(input).toBeTruthy();
+
+ dispatchKeyDown(input!, "Enter");
+ expect(fetchMock).not.toHaveBeenCalled();
+ });
+
+ it("triggers validation on Enter key press when API key is provided", async () => {
+ const fetchMock = vi.fn().mockResolvedValue({
+ ok: true,
+ json: async () => ({ valid: true }),
+ });
+ global.fetch = fetchMock as any;
+
+ const el = render({});
+ const input = el.querySelector('input[type="password"]');
+ expect(input).toBeTruthy();
+
+ setInputValue(input!, "sk-test1234567890");
+ dispatchKeyDown(input!, "Enter");
+
+ expect(fetchMock).toHaveBeenCalledWith(
+ "/api/providers/validate",
+ expect.objectContaining({
+ method: "POST",
+ body: expect.stringContaining("sk-test1234567890"),
+ })
+ );
+ });
+});