diff --git a/src/app/(dashboard)/dashboard/providers/[id]/components/NoAuthProviderControls.tsx b/src/app/(dashboard)/dashboard/providers/[id]/components/NoAuthProviderControls.tsx index 9b04b93e7498..0ebb4d5c0f6f 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/components/NoAuthProviderControls.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/components/NoAuthProviderControls.tsx @@ -174,6 +174,7 @@ export default function NoAuthProviderControls({ return ( ([]); const [loading, setLoading] = useState(true); diff --git a/src/shared/components/NoAuthProviderCard.tsx b/src/shared/components/NoAuthProviderCard.tsx index 77f05aaf6576..6c6d6b2f407f 100644 --- a/src/shared/components/NoAuthProviderCard.tsx +++ b/src/shared/components/NoAuthProviderCard.tsx @@ -2,23 +2,42 @@ import { useTranslations } from "next-intl"; import type { ReactNode } from "react"; +import { NOAUTH_PROVIDERS } from "@/shared/constants/providers/noauth"; import Card from "./Card"; import NoAuthProviderToggle from "./NoAuthProviderToggle"; interface NoAuthProviderCardProps { + providerId?: string; enabled?: boolean; saving?: boolean; onEnabledChange?: (enabled: boolean) => void; providerProxyControl?: ReactNode; } +export function resolveNoAuthBannerDescription( + providerId: string | undefined, + fallback: string +): string { + const provider = + providerId && providerId in NOAUTH_PROVIDERS + ? NOAUTH_PROVIDERS[providerId as keyof typeof NOAUTH_PROVIDERS] + : undefined; + const requirement = + provider && "notice" in provider && typeof provider.notice?.text === "string" + ? provider.notice.text.trim() + : undefined; + return requirement ? `${fallback} ${requirement}` : fallback; +} + export default function NoAuthProviderCard({ + providerId, enabled = true, saving = false, onEnabledChange, providerProxyControl, }: NoAuthProviderCardProps) { const t = useTranslations("noAuthProvider"); + const description = resolveNoAuthBannerDescription(providerId, t("description")); return ( @@ -29,7 +48,7 @@ export default function NoAuthProviderCard({

{t("title")}

-

{t("description")}

+

{description}

diff --git a/tests/unit/noauth-banner-requirement.test.ts b/tests/unit/noauth-banner-requirement.test.ts new file mode 100644 index 000000000000..ce41fa42bf3e --- /dev/null +++ b/tests/unit/noauth-banner-requirement.test.ts @@ -0,0 +1,52 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import React from "react"; +import { renderToStaticMarkup as reactRenderToStaticMarkup } from "react-dom/server"; +import { NextIntlClientProvider } from "next-intl"; +import enMessages from "../../src/i18n/messages/en.json" with { type: "json" }; +import { NOAUTH_PROVIDERS } from "../../src/shared/constants/providers/noauth.ts"; +import NoAuthProviderCard from "../../src/shared/components/NoAuthProviderCard.tsx"; +import NoAuthAccountCard from "../../src/shared/components/NoAuthAccountCard.tsx"; + +const PLAIN = enMessages.noAuthProvider.description; + +function renderBanner(providerId: keyof typeof NOAUTH_PROVIDERS) { + return reactRenderToStaticMarkup( + React.createElement( + NextIntlClientProvider, + { locale: "en", timeZone: "UTC", messages: { noAuthProvider: enMessages.noAuthProvider } }, + React.createElement(NoAuthProviderCard, { providerId, enabled: true }) + ) + ); +} + +test("a no-auth banner includes the provider notice when one is stored", () => { + const html = renderBanner("cloudflare-playground"); + + assert.match(html, /ready to use immediately/); + assert.match(html, /Requires Playwright with a Chromium browser on first request/); +}); + +test("a no-auth banner stays the plain ready message when the provider has no notice or auth hint", () => { + const html = renderBanner("duckduckgo-web"); + + assert.match(html, new RegExp(PLAIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))); + assert.doesNotMatch(html, /DuckDuckGo AI Chat is anonymous/); +}); + +test("an account no-auth banner includes the stored notice beside the account message", () => { + const html = reactRenderToStaticMarkup( + React.createElement( + NextIntlClientProvider, + { locale: "en", timeZone: "UTC", messages: { noAuthProvider: enMessages.noAuthProvider } }, + React.createElement(NoAuthAccountCard, { + providerId: "opencode", + providerName: "OpenCode", + generateAccountId: () => "account-1", + }) + ) + ); + + assert.match(html, /Ready to use/); + assert.match(html, /requests that do not match the OpenCode client contract/); +});