Skip to content

Commit 79b8ff9

Browse files
committed
test(shadcn): Add sign in auth screen test example
1 parent 0a35684 commit 79b8ff9

File tree

4 files changed

+384
-72
lines changed

4 files changed

+384
-72
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as React from "react";
2+
3+
import { cn } from "@/lib/utils";
4+
5+
function Card({ className, ...props }: React.ComponentProps<"div">) {
6+
return (
7+
<div
8+
data-slot="card"
9+
className={cn("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", className)}
10+
{...props}
11+
/>
12+
);
13+
}
14+
15+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
16+
return (
17+
<div
18+
data-slot="card-header"
19+
className={cn(
20+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
21+
className
22+
)}
23+
{...props}
24+
/>
25+
);
26+
}
27+
28+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
29+
return <div data-slot="card-title" className={cn("leading-none font-semibold", className)} {...props} />;
30+
}
31+
32+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
33+
return <div data-slot="card-description" className={cn("text-muted-foreground text-sm", className)} {...props} />;
34+
}
35+
36+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
37+
return (
38+
<div
39+
data-slot="card-action"
40+
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
41+
{...props}
42+
/>
43+
);
44+
}
45+
46+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
47+
return <div data-slot="card-content" className={cn("px-6", className)} {...props} />;
48+
}
49+
50+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
51+
return (
52+
<div data-slot="card-footer" className={cn("flex items-center px-6 [.border-t]:pt-6", className)} {...props} />
53+
);
54+
}
55+
56+
export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
18+
import { render, screen, cleanup } from "@testing-library/react";
19+
import { SignInAuthScreen } from "./sign-in-auth-screen";
20+
import { createMockUI, createFirebaseUIProvider } from "../../tests/utils";
21+
import { registerLocale } from "@firebase-ui/translations";
22+
import { FirebaseUIProvider } from "@firebase-ui/react";
23+
24+
vi.mock("./sign-in-auth-form", async (importOriginal) => {
25+
const mod = await importOriginal<typeof import("./sign-in-auth-form")>();
26+
return {
27+
...mod,
28+
SignInAuthForm: (props: any) => {
29+
const OriginalForm = mod.SignInAuthForm;
30+
return (
31+
<div data-testid="sign-in-auth-form">
32+
<OriginalForm {...props} />
33+
</div>
34+
);
35+
},
36+
};
37+
});
38+
39+
vi.mock("@/components/ui/card", () => ({
40+
Card: ({ children }: { children: React.ReactNode }) => <div data-testid="card">{children}</div>,
41+
CardHeader: ({ children }: { children: React.ReactNode }) => <div data-testid="card-header">{children}</div>,
42+
CardTitle: ({ children }: { children: React.ReactNode }) => <h2 data-testid="card-title">{children}</h2>,
43+
CardDescription: ({ children }: { children: React.ReactNode }) => <p data-testid="card-description">{children}</p>,
44+
CardContent: ({ children }: { children: React.ReactNode }) => <div data-testid="card-content">{children}</div>,
45+
}));
46+
47+
vi.mock("@/components/ui/separator", () => ({
48+
Separator: ({ children }: { children: React.ReactNode }) => <div data-testid="separator">{children}</div>,
49+
}));
50+
51+
describe("<SignInAuthScreen />", () => {
52+
beforeEach(() => {
53+
vi.clearAllMocks();
54+
});
55+
56+
afterEach(() => {
57+
cleanup();
58+
});
59+
60+
it("should render the screen with title and subtitle correctly", () => {
61+
const mockUI = createMockUI({
62+
locale: registerLocale("test", {
63+
labels: {
64+
signIn: "Sign In",
65+
},
66+
prompts: {
67+
signInToAccount: "Sign in to your account",
68+
},
69+
}),
70+
});
71+
72+
render(
73+
<FirebaseUIProvider ui={mockUI}>
74+
<SignInAuthScreen />
75+
</FirebaseUIProvider>
76+
);
77+
78+
expect(screen.getByTestId("card")).toBeInTheDocument();
79+
expect(screen.getByTestId("card-header")).toBeInTheDocument();
80+
expect(screen.getByTestId("card-content")).toBeInTheDocument();
81+
82+
expect(screen.getByTestId("card-title")).toHaveTextContent("Sign In");
83+
expect(screen.getByTestId("card-description")).toHaveTextContent("Sign in to your account");
84+
});
85+
86+
it("should render the SignInAuthForm within the card content", () => {
87+
const mockUI = createMockUI();
88+
89+
render(
90+
<FirebaseUIProvider ui={mockUI}>
91+
<SignInAuthScreen />
92+
</FirebaseUIProvider>
93+
);
94+
95+
expect(screen.getByTestId("sign-in-auth-form")).toBeInTheDocument();
96+
expect(screen.getByTestId("card-content")).toContainElement(screen.getByTestId("sign-in-auth-form"));
97+
});
98+
99+
it("should not render separator and children section when no children provided", () => {
100+
const mockUI = createMockUI();
101+
102+
render(
103+
<FirebaseUIProvider ui={mockUI}>
104+
<SignInAuthScreen />
105+
</FirebaseUIProvider>
106+
);
107+
108+
expect(screen.queryByTestId("separator")).not.toBeInTheDocument();
109+
expect(screen.queryByText("dividerOr")).not.toBeInTheDocument();
110+
});
111+
112+
it("should render children with separator when children are provided", () => {
113+
const mockUI = createMockUI({
114+
locale: registerLocale("test", {
115+
messages: {
116+
dividerOr: "or",
117+
},
118+
}),
119+
});
120+
121+
const TestChild = () => <div data-testid="test-child">Test Child Component</div>;
122+
123+
render(
124+
<FirebaseUIProvider ui={mockUI}>
125+
<SignInAuthScreen>
126+
<TestChild />
127+
</SignInAuthScreen>
128+
</FirebaseUIProvider>
129+
);
130+
131+
// Check that separator is rendered with correct translation
132+
expect(screen.getByTestId("separator")).toBeInTheDocument();
133+
expect(screen.getByTestId("separator")).toHaveTextContent("or");
134+
135+
// Check that children are rendered in a space-y-2 container
136+
expect(screen.getByTestId("test-child")).toBeInTheDocument();
137+
expect(screen.getByTestId("test-child")).toHaveTextContent("Test Child Component");
138+
});
139+
140+
it("should forward props to SignInAuthForm", () => {
141+
const mockUI = createMockUI();
142+
const onForgotPasswordClickMock = vi.fn();
143+
const onRegisterClickMock = vi.fn();
144+
145+
render(
146+
<FirebaseUIProvider ui={mockUI}>
147+
<SignInAuthScreen onForgotPasswordClick={onForgotPasswordClickMock} onRegisterClick={onRegisterClickMock} />
148+
</FirebaseUIProvider>
149+
);
150+
151+
const form = screen.getByTestId("sign-in-auth-form");
152+
expect(form).toBeInTheDocument();
153+
});
154+
155+
it("should render multiple children correctly", () => {
156+
const mockUI = createMockUI({
157+
locale: registerLocale("test", {
158+
messages: {
159+
dividerOr: "or",
160+
},
161+
}),
162+
});
163+
164+
const TestChild1 = () => <div data-testid="test-child-1">Child 1</div>;
165+
const TestChild2 = () => <div data-testid="test-child-2">Child 2</div>;
166+
167+
render(
168+
<FirebaseUIProvider ui={mockUI}>
169+
<SignInAuthScreen>
170+
<TestChild1 />
171+
<TestChild2 />
172+
</SignInAuthScreen>
173+
</FirebaseUIProvider>
174+
);
175+
176+
expect(screen.getByTestId("separator")).toBeInTheDocument();
177+
178+
expect(screen.getByTestId("test-child-1")).toBeInTheDocument();
179+
expect(screen.getByTestId("test-child-2")).toBeInTheDocument();
180+
expect(screen.getByTestId("test-child-1")).toHaveTextContent("Child 1");
181+
expect(screen.getByTestId("test-child-2")).toHaveTextContent("Child 2");
182+
});
183+
184+
it("should handle empty children array", () => {
185+
const mockUI = createMockUI();
186+
187+
render(
188+
<FirebaseUIProvider ui={mockUI}>
189+
<SignInAuthScreen>{[]}</SignInAuthScreen>
190+
</FirebaseUIProvider>
191+
);
192+
193+
expect(screen.getByTestId("separator")).toBeInTheDocument();
194+
});
195+
196+
it("should not render separator when children is null", () => {
197+
const mockUI = createMockUI();
198+
199+
render(
200+
<FirebaseUIProvider ui={mockUI}>
201+
<SignInAuthScreen />
202+
</FirebaseUIProvider>
203+
);
204+
205+
expect(screen.queryByTestId("separator")).not.toBeInTheDocument();
206+
});
207+
208+
it("should use default translations when custom locale is not provided", () => {
209+
const mockUI = createMockUI();
210+
211+
render(
212+
<FirebaseUIProvider ui={mockUI}>
213+
<SignInAuthScreen />
214+
</FirebaseUIProvider>
215+
);
216+
217+
expect(screen.getByTestId("card-title")).toBeInTheDocument();
218+
expect(screen.getByTestId("card-description")).toBeInTheDocument();
219+
expect(screen.getByTestId("sign-in-auth-form")).toBeInTheDocument();
220+
});
221+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client";
2+
3+
import { useUI, SignInAuthScreenProps } from "@firebase-ui/react";
4+
import { getTranslation } from "@firebase-ui/core";
5+
6+
import { SignInAuthForm } from "@/registry/sign-in-auth-form";
7+
import { Separator } from "@/components/ui/separator";
8+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
9+
10+
export type { SignInAuthScreenProps };
11+
12+
export function SignInAuthScreen({ children, ...props }: SignInAuthScreenProps) {
13+
const ui = useUI();
14+
15+
const titleText = getTranslation(ui, "labels", "signIn");
16+
const subtitleText = getTranslation(ui, "prompts", "signInToAccount");
17+
18+
return (
19+
<Card>
20+
<CardHeader>
21+
<CardTitle>{titleText}</CardTitle>
22+
<CardDescription>{subtitleText}</CardDescription>
23+
</CardHeader>
24+
<CardContent>
25+
<SignInAuthForm {...props} />
26+
{children ? (
27+
<>
28+
<Separator>{getTranslation(ui, "messages", "dividerOr")}</Separator>
29+
<div className="space-y-2">{children}</div>
30+
</>
31+
) : null}
32+
</CardContent>
33+
</Card>
34+
);
35+
}

0 commit comments

Comments
 (0)