-
Ativar espera no servidor
+
Enable server-side wait
- {value.enabled ? "Ativado" : "Desativado"}
+ {value.enabled ? "Enabled" : "Disabled"}
-
Tempo máximo de espera por tentativa
+
Maximum wait per retry
{value.maxRetryWaitSec}s
diff --git a/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx b/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx
index c80b875ed6..e55d2a7073 100644
--- a/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx
+++ b/src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx
@@ -3,6 +3,7 @@
import { useEffect, useMemo, useState } from "react";
import { Button, Card, Collapsible, Input, Select, Toggle } from "@/shared/components";
import { useTranslations } from "next-intl";
+import { useNotificationStore } from "@/store/notificationStore";
import FallbackChainsEditor from "./FallbackChainsEditor";
import {
CLI_COMPAT_PROVIDER_DISPLAY,
@@ -629,6 +630,7 @@ export default function RoutingTab() {
const [lkgpCacheLoading, setLkgpCacheLoading] = useState(false);
const [lkgpCacheStatus, setLkgpCacheStatus] = useState({ type: "", message: "" });
const t = useTranslations("settings");
+ const notify = useNotificationStore();
useEffect(() => {
fetch("/api/settings")
@@ -670,11 +672,15 @@ export default function RoutingTab() {
} catch {
// body wasn't JSON — keep the HTTP status fallback
}
+ notify.error(t("saveFailed"), serverMsg);
if (onError) onError(serverMsg);
else console.error("Failed to update settings:", serverMsg);
+ } else {
+ notify.success(t("savedSuccessfully"));
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
+ notify.error(t("saveFailed"), msg);
if (onError) onError(msg);
else console.error("Failed to update settings:", msg);
}
diff --git a/src/app/(dashboard)/dashboard/settings/page.tsx b/src/app/(dashboard)/dashboard/settings/page.tsx
index ccc4369dcf..ecc54f1133 100644
--- a/src/app/(dashboard)/dashboard/settings/page.tsx
+++ b/src/app/(dashboard)/dashboard/settings/page.tsx
@@ -45,7 +45,7 @@ export default function SettingsPage() {
{/* Tab navigation */}
-
+
({
+ useTranslations: () => (key: string, values?: Record
) => {
+ if (values && typeof values.count !== "undefined") {
+ return `${values.count} ${key}`;
+ }
+ return key;
+ },
+}));
+
+const cleanupCallbacks: Array<() => void> = [];
+
+function makeContainer(): HTMLElement {
+ const container = document.createElement("div");
+ document.body.appendChild(container);
+ cleanupCallbacks.push(() => {
+ container.remove();
+ });
+ return container;
+}
+
+describe("AutoComboCatalog", () => {
+ beforeEach(() => {
+ (
+ globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
+ ).IS_REACT_ACT_ENVIRONMENT = true;
+ });
+
+ afterEach(() => {
+ while (cleanupCallbacks.length > 0) {
+ cleanupCallbacks.pop()?.();
+ }
+ document.body.innerHTML = "";
+ });
+
+ it("renders the header with translated title and template-count badge", async () => {
+ const { default: AutoComboCatalog } =
+ await import("@/app/(dashboard)/dashboard/combos/AutoComboCatalog");
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render();
+ });
+ expect(container.textContent).toContain("autoCatalogTitle");
+ expect(container.textContent).toContain(
+ `${AUTO_COMBO_TEMPLATES.length} autoCatalogTemplateCount`
+ );
+ });
+
+ it("stays collapsed by default — no template rows in the DOM", async () => {
+ const { default: AutoComboCatalog } =
+ await import("@/app/(dashboard)/dashboard/combos/AutoComboCatalog");
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render();
+ });
+ const first = AUTO_COMBO_TEMPLATES[0];
+ expect(container.textContent ?? "").not.toContain(first.name);
+ });
+
+ it("expands when toggled and lists every template name", async () => {
+ const { default: AutoComboCatalog } =
+ await import("@/app/(dashboard)/dashboard/combos/AutoComboCatalog");
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render();
+ });
+ const toggle = container.querySelector("button");
+ expect(toggle).toBeTruthy();
+ await act(async () => {
+ toggle?.click();
+ });
+ for (const tpl of AUTO_COMBO_TEMPLATES) {
+ expect(container.textContent ?? "").toContain(tpl.name);
+ }
+ });
+
+ it("flips the toggle aria-label between expand and collapse", async () => {
+ const { default: AutoComboCatalog } =
+ await import("@/app/(dashboard)/dashboard/combos/AutoComboCatalog");
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render();
+ });
+ const toggle = container.querySelector("button");
+ expect(toggle?.getAttribute("aria-label")).toBe("autoCatalogExpand");
+ expect(toggle?.getAttribute("aria-expanded")).toBe("false");
+ await act(async () => {
+ toggle?.click();
+ });
+ expect(toggle?.getAttribute("aria-label")).toBe("autoCatalogCollapse");
+ expect(toggle?.getAttribute("aria-expanded")).toBe("true");
+ });
+
+ it("renders the strategy badge for each template when expanded", async () => {
+ const { default: AutoComboCatalog } =
+ await import("@/app/(dashboard)/dashboard/combos/AutoComboCatalog");
+ const container = makeContainer();
+ const root = createRoot(container);
+ await act(async () => {
+ root.render();
+ });
+ await act(async () => {
+ (container.querySelector("button") as HTMLButtonElement | null)?.click();
+ });
+ const strategies = new Set(AUTO_COMBO_TEMPLATES.map((t) => t.strategy));
+ for (const s of strategies) {
+ expect(container.textContent ?? "").toContain(s);
+ }
+ });
+});