mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-23 07:32:20 +03:00
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:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()"
|
||||
);
|
||||
});
|
||||
});
|
||||
104
tests/unit/ui/add-api-key-modal-enter-key.test.tsx
Normal file
104
tests/unit/ui/add-api-key-modal-enter-key.test.tsx
Normal 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"),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user