mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 19:32:20 +03:00
* fix(dashboard): make provider card warning indicators expose the interaction they advertise The usage-risk indicator (subscriptionRisk) promised "click for details" in its tooltip but was a bare <span> with no onClick/role/dialog. The connection warning-count badge exposed neither a title tooltip (reasons) nor any click affordance, even though the reasons already exist in providerSpecificData.apiKeyHealth[]. Turn the risk indicator into a real <button role/aria-haspopup="dialog"> that opens an accessible Modal reusing the existing riskNotice copy, and wrap the warning badge in a keyboard- and pointer-interactive control that surfaces a sanitized reasons summary (max failure count + relative last-failure time, never raw upstream error text) and navigates to the connection detail/health view on activation. Both indicators are now visually distinct (bare icon vs. pill Badge). Closes #10261 * i18n(providers): sync riskNotice.detailsTitle + warningNotice keys to all 42 locales (#10261) Real Vietnamese translations (vi.json has a strict no-__MISSING__-marker gate); other 41 locales carry the sync-ui __MISSING__ placeholder pending the normal translation pass. * test(dashboard): relocate provider warning regression test Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
120 lines
5.2 KiB
TypeScript
120 lines
5.2 KiB
TypeScript
// #10261 — provider warning badges advertised interaction they did not implement:
|
|
// (1) the usage-risk `subscriptionRisk` indicator promised "click for details"
|
|
// (`providers.riskNotice.tooltip`) but was a bare <span> with no onClick/role/dialog;
|
|
// (2) the connection warning-count badge exposed neither a `title` (reasons) nor any
|
|
// click affordance, even though the reasons already exist in
|
|
// `providerSpecificData.apiKeyHealth[]` (see EditConnectionModal.tsx).
|
|
//
|
|
// This is the permanent regression guard for both defects, extended (per the plan-file's
|
|
// implementation checkbox) to also assert KEYBOARD activation (focus + Enter), not just
|
|
// pointer click, for the interactive risk indicator.
|
|
import React from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import { act } from "react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import ProviderCard from "../../../../../src/app/(dashboard)/dashboard/providers/components/ProviderCard";
|
|
|
|
vi.mock("@/shared/components/ProviderTestSlideOver", () => ({ default: () => null }));
|
|
vi.mock("@/shared/components/ProviderIcon", () => ({ default: () => null }));
|
|
vi.mock("next/navigation", () => ({ useRouter: () => ({ push: () => {} }) }));
|
|
|
|
describe("ProviderCard — #10261 warning indicator consistency", () => {
|
|
let container: HTMLDivElement | null = null;
|
|
let root: ReturnType<typeof createRoot> | null = null;
|
|
|
|
function renderCard(props: Record<string, unknown> = {}) {
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
root = createRoot(container);
|
|
act(() => {
|
|
root!.render(
|
|
<ProviderCard
|
|
providerId="openai"
|
|
provider={{ id: "openai", name: "OpenAI", subscriptionRisk: true }}
|
|
stats={{ total: 2, connected: 1, error: 0, warning: 2, warningMaxFailures: 5 }}
|
|
authType="oauth"
|
|
onToggle={() => {}}
|
|
{...props}
|
|
/>
|
|
);
|
|
});
|
|
return { container: container!, root: root! };
|
|
}
|
|
|
|
afterEach(() => {
|
|
if (root) {
|
|
act(() => root!.unmount());
|
|
root = null;
|
|
}
|
|
if (container) {
|
|
document.body.removeChild(container);
|
|
container = null;
|
|
}
|
|
});
|
|
|
|
it("usage-risk indicator advertised as 'click for details' opens an accessible dialog on click", () => {
|
|
const { container: el } = renderCard();
|
|
const riskEl = el.querySelector('[aria-label*="click for details"]');
|
|
expect(riskEl).toBeTruthy();
|
|
expect(el.querySelector('[role="dialog"]')).toBeFalsy();
|
|
act(() => {
|
|
riskEl!.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
});
|
|
const dialog = el.querySelector('[role="dialog"]');
|
|
expect(dialog).toBeTruthy();
|
|
// The dialog must not leak raw credential values.
|
|
expect(dialog!.textContent || "").not.toMatch(/sk-[a-zA-Z0-9]/);
|
|
});
|
|
|
|
it("usage-risk indicator is a real interactive control reachable and activatable by keyboard", () => {
|
|
const { container: el } = renderCard();
|
|
const riskEl = el.querySelector('[aria-label*="click for details"]') as HTMLElement;
|
|
expect(riskEl).toBeTruthy();
|
|
// Must be a real interactive element — a <button> or an explicit role="button" —
|
|
// not a bare <span> that keyboard users can never reach.
|
|
const isRealButton = riskEl.tagName.toLowerCase() === "button";
|
|
const hasButtonRole = riskEl.getAttribute("role") === "button";
|
|
expect(isRealButton || hasButtonRole).toBe(true);
|
|
riskEl.focus();
|
|
expect(document.activeElement).toBe(riskEl);
|
|
act(() => {
|
|
riskEl.dispatchEvent(
|
|
new KeyboardEvent("keydown", { key: "Enter", bubbles: true, cancelable: true })
|
|
);
|
|
});
|
|
expect(el.querySelector('[role="dialog"]')).toBeTruthy();
|
|
});
|
|
|
|
it("warning-count badge exposes sanitized reasons and a distinct click/keyboard affordance", () => {
|
|
const { container: el } = renderCard();
|
|
const badge = Array.from(el.querySelectorAll("span")).find((node) =>
|
|
(node.textContent || "").includes("Warning")
|
|
);
|
|
expect(badge).toBeTruthy();
|
|
const hasReasonsTooltip = Boolean(badge!.getAttribute("title"));
|
|
const isInteractive = Boolean(
|
|
badge!.getAttribute("role") === "button" || badge!.tagName.toLowerCase() === "button"
|
|
);
|
|
expect(hasReasonsTooltip || isInteractive).toBeTruthy();
|
|
// Never render raw upstream error text/credentials in the tooltip.
|
|
const title = badge!.getAttribute("title") || "";
|
|
expect(title).not.toMatch(/sk-[a-zA-Z0-9]/);
|
|
// Reachable and activatable by keyboard too.
|
|
expect(badge!.getAttribute("tabindex")).toBe("0");
|
|
});
|
|
|
|
it("neither the risk indicator nor the warning badge use identical amber icon styling", () => {
|
|
const { container: el } = renderCard();
|
|
const riskEl = el.querySelector('[aria-label*="click for details"]');
|
|
const badge = Array.from(el.querySelectorAll("span")).find((node) =>
|
|
(node.textContent || "").includes("Warning")
|
|
);
|
|
expect(riskEl).toBeTruthy();
|
|
expect(badge).toBeTruthy();
|
|
// The risk indicator keeps the bare material-symbols glyph; the warning badge is
|
|
// rendered through the pill-shaped Badge component — distinct visual language.
|
|
expect(riskEl!.className).toContain("material-symbols-outlined");
|
|
expect(badge!.className).not.toContain("material-symbols-outlined");
|
|
});
|
|
});
|