refactor(dashboard): mirror check button disable state in AddApiKeyModal Enter handler (#10995) (#11156)

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!
This commit is contained in:
Rouzbeh†
2026-08-23 02:43:10 +03:30
committed by GitHub
parent 64b7389fe9
commit d3ac1a600c
4 changed files with 147 additions and 66 deletions

View File

@@ -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,

View File

@@ -757,46 +757,48 @@ export default function AddApiKeyModal({
onImport={(apiKey) => setFormData({ ...formData, apiKey })}
/>
)}
{!isNoAuthWebSessionCredential && (
<div className="flex gap-2">
<Input
label={apiCredentialLabel}
type="password"
value={formData.apiKey}
onChange={(e) => 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"
/>
<div className="pt-6">
<Button
onClick={handleValidate}
disabled={
(!isCompatible && !apiKeyOptional && !formData.apiKey) ||
(isGooglePse && !formData.cx.trim()) ||
validating ||
saving
}
variant="secondary"
>
{validating
? t("checking")
: webSessionCredential
? getWebSessionCredentialCheckLabel(t, webSessionCredential)
: t("check")}
</Button>
{!isNoAuthWebSessionCredential && (() => {
const isCheckDisabled =
(!isCompatible && !apiKeyOptional && !formData.apiKey) ||
(isGooglePse && !formData.cx.trim()) ||
validating ||
saving;
return (
<div className="flex gap-2">
<Input
label={apiCredentialLabel}
type="password"
value={formData.apiKey}
onChange={(e) => 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"
/>
<div className="pt-6">
<Button
onClick={handleValidate}
disabled={isCheckDisabled}
variant="secondary"
>
{validating
? t("checking")
: webSessionCredential
? getWebSessionCredentialCheckLabel(t, webSessionCredential)
: t("check")}
</Button>
</div>
</div>
</div>
)}
);
})()}
{isChatGptWebCodex && (
<div className="space-y-3 rounded-lg border border-border bg-surface/40 p-3">
<div>

View File

@@ -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()"
);
});
});

View File

@@ -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<typeof createRoot>; el: HTMLDivElement }> = [];
function render(props: Record<string, unknown>) {
const el = document.createElement("div");
document.body.appendChild(el);
const root = createRoot(el);
act(() => {
root.render(
<AddApiKeyModal
isOpen
provider="openai"
providerName="OpenAI"
onSave={async () => 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<HTMLInputElement>('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<HTMLInputElement>('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"),
})
);
});
});