Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/react/src/auth/forms/email-link-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ export function EmailLinkAuthForm({ onEmailSent, onSignIn }: EmailLinkAuthFormPr
>
<form.AppForm>
<fieldset>
<form.AppField name="email">{(field) => <field.Input label="Email" type="email" />}</form.AppField>
<form.AppField name="email">
{(field) => <field.Input label={getTranslation(ui, "labels", "emailAddress")} type="email" />}
</form.AppField>
</fieldset>
<Policies />
<fieldset>
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/auth/forms/forgot-password-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ export function ForgotPasswordAuthForm({ onBackToSignInClick, onPasswordSent }:
>
<form.AppForm>
<fieldset>
<form.AppField name="email">{(field) => <field.Input label="Email" type="email" />}</form.AppField>
<form.AppField name="email">
{(field) => <field.Input label={getTranslation(ui, "labels", "emailAddress")} type="email" />}
</form.AppField>
</fieldset>
<Policies />
<fieldset>
Expand Down
6 changes: 4 additions & 2 deletions packages/react/src/auth/forms/sign-in-auth-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,14 @@ export function SignInAuthForm({ onSignIn, onForgotPasswordClick, onRegisterClic
>
<form.AppForm>
<fieldset>
<form.AppField name="email">{(field) => <field.Input label="Email" type="email" />}</form.AppField>
<form.AppField name="email">
{(field) => <field.Input label={getTranslation(ui, "labels", "emailAddress")} type="email" />}
</form.AppField>
</fieldset>
<fieldset>
<form.AppField name="password">
{(field) => (
<field.Input label="Password" type="password">
<field.Input label={getTranslation(ui, "labels", "password")} type="password">
{onForgotPasswordClick ? (
<form.Action onClick={onForgotPasswordClick}>
{getTranslation(ui, "labels", "forgotPassword")}
Expand Down
263 changes: 254 additions & 9 deletions packages/react/src/auth/forms/sign-up-auth-form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, renderHook, cleanup } from "@testing-library/react";
import { SignUpAuthForm, useSignUpAuthForm, useSignUpAuthFormAction } from "./sign-up-auth-form";
import { SignUpAuthForm, useSignUpAuthForm, useSignUpAuthFormAction, useRequireDisplayName } from "./sign-up-auth-form";
import { act } from "react";
import { createUserWithEmailAndPassword } from "@firebase-ui/core";
import { createFirebaseUIProvider, createMockUI } from "~/tests/utils";
Expand Down Expand Up @@ -63,7 +63,8 @@ describe("useSignUpAuthFormAction", () => {
expect(createUserWithEmailAndPasswordMock).toHaveBeenCalledWith(
expect.any(Object),
"[email protected]",
"password123"
"password123",
undefined
);
});

Expand All @@ -88,7 +89,8 @@ describe("useSignUpAuthFormAction", () => {
expect(createUserWithEmailAndPasswordMock).toHaveBeenCalledWith(
expect.any(Object),
"[email protected]",
"password123"
"password123",
undefined
);
});

Expand All @@ -115,7 +117,35 @@ describe("useSignUpAuthFormAction", () => {
});
}).rejects.toThrow("unknownError");

expect(createUserWithEmailAndPasswordMock).toHaveBeenCalledWith(mockUI.get(), "[email protected]", "password123");
expect(createUserWithEmailAndPasswordMock).toHaveBeenCalledWith(
mockUI.get(),
"[email protected]",
"password123",
undefined
);
});

it("should return a callback which accepts email, password, and displayName", async () => {
const mockCredential = { credential: true } as unknown as UserCredential;
const createUserWithEmailAndPasswordMock = vi
.mocked(createUserWithEmailAndPassword)
.mockResolvedValue(mockCredential);
const mockUI = createMockUI();

const { result } = renderHook(() => useSignUpAuthFormAction(), {
wrapper: ({ children }) => createFirebaseUIProvider({ children, ui: mockUI }),
});

await act(async () => {
await result.current({ email: "[email protected]", password: "password123", displayName: "John Doe" });
});

expect(createUserWithEmailAndPasswordMock).toHaveBeenCalledWith(
expect.any(Object),
"[email protected]",
"password123",
"John Doe"
);
});
});

Expand All @@ -129,8 +159,11 @@ describe("useSignUpAuthForm", () => {
});

it("should allow the form to be submitted", async () => {
const mockCredential = { credential: true } as unknown as UserCredential;
const mockUI = createMockUI();
const createUserWithEmailAndPasswordMock = vi.mocked(createUserWithEmailAndPassword);
const createUserWithEmailAndPasswordMock = vi
.mocked(createUserWithEmailAndPassword)
.mockResolvedValue(mockCredential);

const { result } = renderHook(() => useSignUpAuthForm(), {
wrapper: ({ children }) => createFirebaseUIProvider({ children, ui: mockUI }),
Expand All @@ -139,13 +172,19 @@ describe("useSignUpAuthForm", () => {
act(() => {
result.current.setFieldValue("email", "[email protected]");
result.current.setFieldValue("password", "password123");
// Don't set displayName - let it be undefined (optional)
});

await act(async () => {
await result.current.handleSubmit();
});

expect(createUserWithEmailAndPasswordMock).toHaveBeenCalledWith(mockUI.get(), "[email protected]", "password123");
expect(createUserWithEmailAndPasswordMock).toHaveBeenCalledWith(
mockUI.get(),
"[email protected]",
"password123",
undefined
);
});

it("should not allow the form to be submitted if the form is invalid", async () => {
Expand All @@ -167,18 +206,50 @@ describe("useSignUpAuthForm", () => {
expect(result.current.getFieldMeta("email")!.errors[0].length).toBeGreaterThan(0);
expect(createUserWithEmailAndPasswordMock).not.toHaveBeenCalled();
});

it("should allow the form to be submitted with displayName", async () => {
const mockUI = createMockUI();
const createUserWithEmailAndPasswordMock = vi.mocked(createUserWithEmailAndPassword);

const { result } = renderHook(() => useSignUpAuthForm(), {
wrapper: ({ children }) => createFirebaseUIProvider({ children, ui: mockUI }),
});

act(() => {
result.current.setFieldValue("email", "[email protected]");
result.current.setFieldValue("password", "password123");
result.current.setFieldValue("displayName", "John Doe");
});

await act(async () => {
await result.current.handleSubmit();
});

expect(createUserWithEmailAndPasswordMock).toHaveBeenCalledWith(
mockUI.get(),
"[email protected]",
"password123",
"John Doe"
);
});
});

describe("<SignUpAuthForm />", () => {
beforeEach(() => {
vi.clearAllMocks();
});

afterEach(() => {
cleanup();
});

it("should render the form correctly", () => {
const mockUI = createMockUI({
locale: registerLocale("test", {
labels: {
createAccount: "createAccount",
emailAddress: "emailAddress",
password: "password",
},
}),
});
Expand All @@ -193,9 +264,9 @@ describe("<SignUpAuthForm />", () => {
const form = container.querySelectorAll("form.fui-form");
expect(form.length).toBe(1);

// Make sure we have an email and password input
expect(screen.getByRole("textbox", { name: /email/i })).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: /password/i })).toBeInTheDocument();
// Make sure we have an email and password input with translated labels
expect(screen.getByRole("textbox", { name: /emailAddress/ })).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: /password/ })).toBeInTheDocument();

// Ensure the "Create Account" button is present and is a submit button
const createAccountButton = screen.getByRole("button", { name: "createAccount" });
Expand Down Expand Up @@ -256,4 +327,178 @@ describe("<SignUpAuthForm />", () => {

expect(screen.getByText("Please enter a valid email address")).toBeInTheDocument();
});

it("should render displayName field when requireDisplayName behavior is enabled", () => {
const mockUI = createMockUI({
locale: registerLocale("test", {
labels: {
createAccount: "createAccount",
emailAddress: "emailAddress",
password: "password",
displayName: "displayName",
},
}),
behaviors: [
{
requireDisplayName: { type: "callable" as const, handler: vi.fn() },
},
],
});

const { container } = render(
<FirebaseUIProvider ui={mockUI}>
<SignUpAuthForm />
</FirebaseUIProvider>
);

// There should be only one form
const form = container.querySelectorAll("form.fui-form");
expect(form.length).toBe(1);

// Make sure we have all three inputs with translated labels
expect(screen.getByRole("textbox", { name: /emailAddress/ })).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: /password/ })).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: /displayName/ })).toBeInTheDocument();

// Ensure the "Create Account" button is present and is a submit button
const createAccountButton = screen.getByRole("button", { name: "createAccount" });
expect(createAccountButton).toBeInTheDocument();
expect(createAccountButton).toHaveAttribute("type", "submit");
});

it("should not render displayName field when requireDisplayName behavior is not enabled", () => {
const mockUI = createMockUI({
locale: registerLocale("test", {
labels: {
createAccount: "createAccount",
emailAddress: "emailAddress",
password: "password",
displayName: "displayName",
},
}),
behaviors: [], // Explicitly set empty behaviors array
});

const { container } = render(
<FirebaseUIProvider ui={mockUI}>
<SignUpAuthForm />
</FirebaseUIProvider>
);

const form = container.querySelectorAll("form.fui-form");
expect(form.length).toBe(1);

expect(screen.getByRole("textbox", { name: /email/ })).toBeInTheDocument();
expect(screen.getByRole("textbox", { name: /password/ })).toBeInTheDocument();
expect(screen.queryByRole("textbox", { name: /displayName/ })).not.toBeInTheDocument();
});

it("should trigger displayName validation errors when the form is blurred and requireDisplayName is enabled", () => {
const mockUI = createMockUI({
locale: registerLocale("test", {
errors: {
displayNameRequired: "Please provide a display name",
},
labels: {
displayName: "displayName",
},
}),
behaviors: [
{
requireDisplayName: { type: "callable" as const, handler: vi.fn() },
},
],
});

const { container } = render(
<FirebaseUIProvider ui={mockUI}>
<SignUpAuthForm />
</FirebaseUIProvider>
);

const form = container.querySelector("form.fui-form");
expect(form).toBeInTheDocument();

const displayNameInput = screen.getByRole("textbox", { name: /displayName/ });
expect(displayNameInput).toBeInTheDocument();

act(() => {
fireEvent.blur(displayNameInput);
});

expect(screen.getByText("Please provide a display name")).toBeInTheDocument();
});

it("should not trigger displayName validation when requireDisplayName is not enabled", () => {
const mockUI = createMockUI({
locale: registerLocale("test", {
errors: {
displayNameRequired: "Please provide a display name",
},
labels: {
displayName: "displayName",
},
}),
});

const { container } = render(
<FirebaseUIProvider ui={mockUI}>
<SignUpAuthForm />
</FirebaseUIProvider>
);

const form = container.querySelector("form.fui-form");
expect(form).toBeInTheDocument();

// Display name field should not be present
expect(screen.queryByRole("textbox", { name: "displayName" })).not.toBeInTheDocument();
});
});

describe("useRequireDisplayName", () => {
beforeEach(() => {
vi.clearAllMocks();
});

afterEach(() => {
cleanup();
});

it("should return true when requireDisplayName behavior is enabled", () => {
const mockUI = createMockUI({
behaviors: [
{
requireDisplayName: { type: "callable" as const, handler: vi.fn() },
},
],
});

const { result } = renderHook(() => useRequireDisplayName(), {
wrapper: ({ children }) => createFirebaseUIProvider({ children, ui: mockUI }),
});

expect(result.current).toBe(true);
});

it("should return false when requireDisplayName behavior is not enabled", () => {
const mockUI = createMockUI({
behaviors: [],
});

const { result } = renderHook(() => useRequireDisplayName(), {
wrapper: ({ children }) => createFirebaseUIProvider({ children, ui: mockUI }),
});

expect(result.current).toBe(false);
});

it("should return false when behaviors array is empty", () => {
const mockUI = createMockUI();

const { result } = renderHook(() => useRequireDisplayName(), {
wrapper: ({ children }) => createFirebaseUIProvider({ children, ui: mockUI }),
});

expect(result.current).toBe(false);
});
});
Loading