diff --git a/packages/react/src/auth/screens/email-link-auth-screen.tsx b/packages/react/src/auth/screens/email-link-auth-screen.tsx index 024a168f..d2671a25 100644 --- a/packages/react/src/auth/screens/email-link-auth-screen.tsx +++ b/packages/react/src/auth/screens/email-link-auth-screen.tsx @@ -41,7 +41,7 @@ export function EmailLinkAuthScreen({ children, onEmailSent }: EmailLinkAuthScre {children ? ( <> {getTranslation(ui, "messages", "dividerOr")} -
{children}
+
{children}
) : null} diff --git a/packages/react/src/auth/screens/oauth-screen.tsx b/packages/react/src/auth/screens/oauth-screen.tsx index 74fa36cb..cace4fdd 100644 --- a/packages/react/src/auth/screens/oauth-screen.tsx +++ b/packages/react/src/auth/screens/oauth-screen.tsx @@ -35,7 +35,7 @@ export function OAuthScreen({ children }: OAuthScreenProps) { {titleText} {subtitleText} - + {children} diff --git a/packages/react/src/auth/screens/phone-auth-screen.tsx b/packages/react/src/auth/screens/phone-auth-screen.tsx index 31027b90..32c99f82 100644 --- a/packages/react/src/auth/screens/phone-auth-screen.tsx +++ b/packages/react/src/auth/screens/phone-auth-screen.tsx @@ -41,7 +41,7 @@ export function PhoneAuthScreen({ children, ...props }: PhoneAuthScreenProps) { {children ? ( <> {getTranslation(ui, "messages", "dividerOr")} -
{children}
+
{children}
) : null}
diff --git a/packages/react/src/auth/screens/sign-in-auth-screen.tsx b/packages/react/src/auth/screens/sign-in-auth-screen.tsx index 6db02ae1..91aa1662 100644 --- a/packages/react/src/auth/screens/sign-in-auth-screen.tsx +++ b/packages/react/src/auth/screens/sign-in-auth-screen.tsx @@ -41,7 +41,7 @@ export function SignInAuthScreen({ children, ...props }: SignInAuthScreenProps) {children ? ( <> {getTranslation(ui, "messages", "dividerOr")} -
{children}
+
{children}
) : null} diff --git a/packages/react/src/auth/screens/sign-up-auth-screen.tsx b/packages/react/src/auth/screens/sign-up-auth-screen.tsx index 9419e8a3..1d959a52 100644 --- a/packages/react/src/auth/screens/sign-up-auth-screen.tsx +++ b/packages/react/src/auth/screens/sign-up-auth-screen.tsx @@ -41,7 +41,7 @@ export function SignUpAuthScreen({ children, ...props }: SignUpAuthScreenProps) {children ? ( <> {getTranslation(ui, "messages", "dividerOr")} -
{children}
+
{children}
) : null} diff --git a/packages/react/src/components/policies.test.tsx b/packages/react/src/components/policies.test.tsx index 5d9b8800..86ba7bd4 100644 --- a/packages/react/src/components/policies.test.tsx +++ b/packages/react/src/components/policies.test.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ -import { describe, it, expect, vi, beforeEach } from "vitest"; -import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { render, fireEvent, cleanup } from "@testing-library/react"; import { Policies } from "./policies"; import { FirebaseUIProvider } from "~/context"; import { createMockUI } from "~/tests/utils"; @@ -25,8 +25,12 @@ describe("", () => { vi.clearAllMocks(); }); - it("renders component with terms and privacy links", () => { - render( + afterEach(() => { + cleanup(); + }); + + it("renders component with terms and privacy links using anchor tags", () => { + const { container } = render( ", () => { ); // Check that the text and links are rendered - expect(screen.getByText(/By continuing, you agree to our/)).toBeInTheDocument(); + expect(container.querySelector(".fui-policies")).toBeInTheDocument(); - const tosLink = screen.getByText("Terms of Service"); + const tosLink = container.querySelector('a[href="https://example.com/terms"]'); expect(tosLink).toBeInTheDocument(); - expect(tosLink.tagName).toBe("A"); + expect(tosLink?.tagName).toBe("A"); expect(tosLink).toHaveAttribute("target", "_blank"); expect(tosLink).toHaveAttribute("rel", "noopener noreferrer"); + expect(tosLink).toHaveTextContent("Terms of Service"); - const privacyLink = screen.getByText("Privacy Policy"); + const privacyLink = container.querySelector('a[href="https://example.com/privacy"]'); expect(privacyLink).toBeInTheDocument(); - expect(privacyLink.tagName).toBe("A"); + expect(privacyLink?.tagName).toBe("A"); expect(privacyLink).toHaveAttribute("target", "_blank"); expect(privacyLink).toHaveAttribute("rel", "noopener noreferrer"); + expect(privacyLink).toHaveTextContent("Privacy Policy"); + }); + + it("renders component with custom navigation handler using buttons", () => { + const mockNavigate = vi.fn(); + const { container } = render( + + + + ); + + // Check that the text and buttons are rendered + expect(container.querySelector(".fui-policies")).toBeInTheDocument(); + + const tosButton = container.querySelector("button"); + expect(tosButton).toBeInTheDocument(); + expect(tosButton?.tagName).toBe("BUTTON"); + expect(tosButton).not.toHaveAttribute("href"); + expect(tosButton).not.toHaveAttribute("target"); + expect(tosButton).toHaveTextContent("Terms of Service"); + + const privacyButton = container.querySelectorAll("button")[1]; + expect(privacyButton).toBeInTheDocument(); + expect(privacyButton?.tagName).toBe("BUTTON"); + expect(privacyButton).not.toHaveAttribute("href"); + expect(privacyButton).not.toHaveAttribute("target"); + expect(privacyButton).toHaveTextContent("Privacy Policy"); + + fireEvent.click(tosButton!); + expect(mockNavigate).toHaveBeenCalledWith("https://example.com/terms"); + + fireEvent.click(privacyButton!); + expect(mockNavigate).toHaveBeenCalledWith("https://example.com/privacy"); + }); + + it("handles URL objects correctly", () => { + const termsUrl = new URL("https://example.com/terms"); + const privacyUrl = new URL("https://example.com/privacy"); + const { container } = render( + + + + ); + + const tosLink = container.querySelector('a[href="https://example.com/terms"]'); + expect(tosLink).toHaveAttribute("href", "https://example.com/terms"); + + const privacyLink = container.querySelector('a[href="https://example.com/privacy"]'); + expect(privacyLink).toHaveAttribute("href", "https://example.com/privacy"); }); - it("returns null when both tosUrl and privacyPolicyUrl are not provided", () => { + it("returns null when policies are not provided", () => { const { container } = render( @@ -62,4 +129,31 @@ describe("", () => { ); expect(container).toBeEmptyDOMElement(); }); + + it("handles custom navigation with URL objects", () => { + const mockNavigate = vi.fn(); + const termsUrl = new URL("https://example.com/terms"); + const privacyUrl = new URL("https://example.com/privacy"); + const { container } = render( + + + + ); + + const tosButton = container.querySelector("button"); + const privacyButton = container.querySelectorAll("button")[1]; + + fireEvent.click(tosButton!); + expect(mockNavigate).toHaveBeenCalledWith(termsUrl); + + fireEvent.click(privacyButton!); + expect(mockNavigate).toHaveBeenCalledWith(privacyUrl); + }); }); diff --git a/packages/react/src/components/policies.tsx b/packages/react/src/components/policies.tsx index ed9627d6..77da953e 100644 --- a/packages/react/src/components/policies.tsx +++ b/packages/react/src/components/policies.tsx @@ -15,19 +15,15 @@ */ import { getTranslation } from "@firebase-ui/core"; -import { createContext, useContext } from "react"; +import { cloneElement, createContext, useContext } from "react"; import { useUI } from "~/hooks"; -export type PolicyURL = - | string - | URL - | (() => string | URL | void) - | Promise - | (() => Promise); +export type PolicyURL = string | URL; export interface PolicyProps { termsOfServiceUrl: PolicyURL; privacyPolicyUrl: PolicyURL; + onNavigate?: (url: PolicyURL) => void; } const PolicyContext = createContext(undefined); @@ -44,64 +40,33 @@ export function Policies() { return null; } - const { termsOfServiceUrl, privacyPolicyUrl } = policies; - - async function handleUrl(urlOrFunction: PolicyURL) { - let url: string | URL | void; - - if (typeof urlOrFunction === "function") { - const urlOrPromise = urlOrFunction(); - if (typeof urlOrPromise === "string" || urlOrPromise instanceof URL) { - url = urlOrPromise; - } else { - url = await urlOrPromise; - } - } else if (urlOrFunction instanceof Promise) { - url = await urlOrFunction; - } else { - url = urlOrFunction; - } - - if (url) { - window.open(url.toString(), "_blank"); - } - } - - const termsText = getTranslation(ui, "labels", "termsOfService"); - const privacyText = getTranslation(ui, "labels", "privacyPolicy"); + const { termsOfServiceUrl, privacyPolicyUrl, onNavigate } = policies; const termsAndPrivacyText = getTranslation(ui, "messages", "termsAndPrivacy"); - const parts = termsAndPrivacyText.split(/(\{tos\}|\{privacy\})/); + const Handler = onNavigate ?