Skip to content

Commit e6eed4d

Browse files
committed
fix(shadcn): Align remaining components
1 parent f97c551 commit e6eed4d

File tree

8 files changed

+952
-196
lines changed

8 files changed

+952
-196
lines changed

packages/shadcn/registry-spec.json

Lines changed: 272 additions & 192 deletions
Large diffs are not rendered by default.

packages/shadcn/registry-spec.test.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, it, expect, vi, afterEach, beforeEach } from "vitest";
22
import fs from "fs";
3+
import { fail } from "assert";
34

45
const regisryFiles = import.meta.glob([
56
"./src/registry/**/*.tsx",
@@ -14,14 +15,26 @@ describe("registry-spec", () => {
1415
expect(() => JSON.parse(registrySpec)).not.toThrow();
1516
});
1617

17-
for (const path of Object.keys(regisryFiles)) {
18-
it(`${path} should exist in the registry`, () => {
18+
const sortedFilePaths = Object.keys(regisryFiles).sort();
19+
20+
for (let i = 0; i < sortedFilePaths.length; i++) {
21+
const path = sortedFilePaths[i];
22+
if (!path) continue;
23+
24+
it(`"${path}" should exist in the registry at the correct index`, () => {
1925
const registrySpec = fs.readFileSync("./registry-spec.json", "utf8");
2026
const json = JSON.parse(registrySpec);
2127

2228
const name = path.split("/").at(-1)?.split(".")[0];
23-
const item = json.items.find((item: any) => item.name === name);
24-
expect(item).toBeDefined();
29+
const index = json.items.findIndex((item: any) => item.name === name);
30+
31+
if (index === -1) {
32+
fail(`"${path}" should exist in the registry`);
33+
}
34+
35+
if (index !== i) {
36+
fail(`"${path}" should be ordered alphabetically based on the name`);
37+
}
2538
});
2639
}
2740
});
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import { describe, it, expect, vi, afterEach } from "vitest";
17+
import { render, screen, cleanup } from "@testing-library/react";
18+
import { OAuthScreen } from "@/registry/oauth-screen";
19+
import { CreateFirebaseUIProvider, createMockUI } from "@/tests/utils";
20+
import { registerLocale } from "@firebase-ui/translations";
21+
import { MultiFactorResolver } from "firebase/auth";
22+
23+
vi.mock("@/registry/policies", () => ({
24+
Policies: () => <div data-testid="policies">Policies</div>,
25+
}));
26+
27+
vi.mock("@/registry/redirect-error", () => ({
28+
RedirectError: () => <div data-testid="redirect-error">Redirect Error</div>,
29+
}));
30+
31+
vi.mock("@/registry/multi-factor-auth-assertion-form", () => ({
32+
MultiFactorAuthAssertionForm: () => <div data-testid="mfa-assertion-form">MFA Assertion Form</div>,
33+
}));
34+
35+
afterEach(() => {
36+
cleanup();
37+
});
38+
39+
describe("<OAuthScreen />", () => {
40+
afterEach(() => {
41+
vi.clearAllMocks();
42+
});
43+
44+
it("renders with correct title and subtitle", () => {
45+
const ui = createMockUI({
46+
locale: registerLocale("test", {
47+
labels: {
48+
signIn: "signIn",
49+
},
50+
prompts: {
51+
signInToAccount: "signInToAccount",
52+
},
53+
}),
54+
});
55+
56+
render(
57+
<CreateFirebaseUIProvider ui={ui}>
58+
<OAuthScreen>OAuth Provider</OAuthScreen>
59+
</CreateFirebaseUIProvider>
60+
);
61+
62+
const title = screen.getByText("signIn");
63+
expect(title).toBeDefined();
64+
65+
const subtitle = screen.getByText("signInToAccount");
66+
expect(subtitle).toBeDefined();
67+
});
68+
69+
it("renders children", () => {
70+
const ui = createMockUI();
71+
72+
render(
73+
<CreateFirebaseUIProvider ui={ui}>
74+
<OAuthScreen>OAuth Provider</OAuthScreen>
75+
</CreateFirebaseUIProvider>
76+
);
77+
78+
expect(screen.getByText("OAuth Provider")).toBeDefined();
79+
});
80+
81+
it("renders multiple children when provided", () => {
82+
const ui = createMockUI();
83+
84+
render(
85+
<CreateFirebaseUIProvider ui={ui}>
86+
<OAuthScreen>
87+
<div>Provider 1</div>
88+
<div>Provider 2</div>
89+
</OAuthScreen>
90+
</CreateFirebaseUIProvider>
91+
);
92+
93+
expect(screen.getByText("Provider 1")).toBeDefined();
94+
expect(screen.getByText("Provider 2")).toBeDefined();
95+
});
96+
97+
it("includes the Policies component", () => {
98+
const ui = createMockUI();
99+
100+
render(
101+
<CreateFirebaseUIProvider ui={ui}>
102+
<OAuthScreen>OAuth Provider</OAuthScreen>
103+
</CreateFirebaseUIProvider>
104+
);
105+
106+
expect(screen.getByTestId("policies")).toBeDefined();
107+
});
108+
109+
it("renders children before the Policies component", () => {
110+
const ui = createMockUI();
111+
112+
render(
113+
<CreateFirebaseUIProvider ui={ui}>
114+
<OAuthScreen>
115+
<div data-testid="oauth-provider">OAuth Provider</div>
116+
</OAuthScreen>
117+
</CreateFirebaseUIProvider>
118+
);
119+
120+
const oauthProvider = screen.getByTestId("oauth-provider");
121+
const policies = screen.getByTestId("policies");
122+
123+
// Both should be present
124+
expect(oauthProvider).toBeDefined();
125+
expect(policies).toBeDefined();
126+
127+
// OAuth provider should come before policies
128+
const cardContent = oauthProvider.parentElement;
129+
const children = Array.from(cardContent?.children || []);
130+
const oauthIndex = children.indexOf(oauthProvider);
131+
const policiesIndex = children.indexOf(policies);
132+
133+
expect(oauthIndex).toBeLessThan(policiesIndex);
134+
});
135+
136+
it("renders MultiFactorAuthAssertionForm when multiFactorResolver is present", () => {
137+
const mockResolver = {
138+
auth: {} as any,
139+
session: null,
140+
hints: [],
141+
};
142+
const ui = createMockUI();
143+
ui.get().setMultiFactorResolver(mockResolver as unknown as MultiFactorResolver);
144+
145+
render(
146+
<CreateFirebaseUIProvider ui={ui}>
147+
<OAuthScreen>OAuth Provider</OAuthScreen>
148+
</CreateFirebaseUIProvider>
149+
);
150+
151+
expect(screen.getByTestId("mfa-assertion-form")).toBeDefined();
152+
expect(screen.queryByText("OAuth Provider")).toBeNull();
153+
expect(screen.queryByTestId("policies")).toBeNull();
154+
});
155+
156+
it("does not render children or Policies when MFA resolver exists", () => {
157+
const mockResolver = {
158+
auth: {} as any,
159+
session: null,
160+
hints: [],
161+
};
162+
const ui = createMockUI();
163+
ui.get().setMultiFactorResolver(mockResolver as unknown as MultiFactorResolver);
164+
165+
render(
166+
<CreateFirebaseUIProvider ui={ui}>
167+
<OAuthScreen>
168+
<div data-testid="oauth-provider">OAuth Provider</div>
169+
</OAuthScreen>
170+
</CreateFirebaseUIProvider>
171+
);
172+
173+
expect(screen.queryByTestId("oauth-provider")).toBeNull();
174+
expect(screen.queryByTestId("policies")).toBeNull();
175+
expect(screen.getByTestId("mfa-assertion-form")).toBeDefined();
176+
});
177+
178+
it("renders RedirectError component with children when no MFA resolver", () => {
179+
const ui = createMockUI();
180+
181+
render(
182+
<CreateFirebaseUIProvider ui={ui}>
183+
<OAuthScreen>
184+
<div data-testid="oauth-provider">OAuth Provider</div>
185+
</OAuthScreen>
186+
</CreateFirebaseUIProvider>
187+
);
188+
189+
expect(screen.getByTestId("redirect-error")).toBeDefined();
190+
expect(screen.getByTestId("oauth-provider")).toBeDefined();
191+
expect(screen.getByTestId("policies")).toBeDefined();
192+
});
193+
194+
it("does not render RedirectError when MFA resolver is present", () => {
195+
const mockResolver = {
196+
auth: {} as any,
197+
session: null,
198+
hints: [],
199+
};
200+
const ui = createMockUI();
201+
ui.get().setMultiFactorResolver(mockResolver as unknown as MultiFactorResolver);
202+
203+
render(
204+
<CreateFirebaseUIProvider ui={ui}>
205+
<OAuthScreen>
206+
<div data-testid="oauth-provider">OAuth Provider</div>
207+
</OAuthScreen>
208+
</CreateFirebaseUIProvider>
209+
);
210+
211+
expect(screen.queryByTestId("redirect-error")).toBeNull();
212+
expect(screen.getByTestId("mfa-assertion-form")).toBeDefined();
213+
});
214+
});
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import { getTranslation } from "@firebase-ui/core";
4+
import { type PropsWithChildren } from "react";
5+
import { useUI } from "@firebase-ui/react";
6+
import { Card, CardContent, CardHeader, CardDescription, CardTitle } from "@/components/ui/card";
7+
import { Policies } from "@/registry/policies";
8+
import { MultiFactorAuthAssertionForm } from "@/registry/multi-factor-auth-assertion-form";
9+
import { RedirectError } from "@/registry/redirect-error";
10+
11+
export type OAuthScreenProps = PropsWithChildren;
12+
13+
export function OAuthScreen({ children }: OAuthScreenProps) {
14+
const ui = useUI();
15+
16+
const titleText = getTranslation(ui, "labels", "signIn");
17+
const subtitleText = getTranslation(ui, "prompts", "signInToAccount");
18+
const mfaResolver = ui.multiFactorResolver;
19+
20+
return (
21+
<div className="max-w-md mx-auto">
22+
<Card>
23+
<CardHeader>
24+
<CardTitle>{titleText}</CardTitle>
25+
<CardDescription>{subtitleText}</CardDescription>
26+
</CardHeader>
27+
<CardContent>
28+
{mfaResolver ? (
29+
<MultiFactorAuthAssertionForm />
30+
) : (
31+
<>
32+
<div className="space-y-2">
33+
{children}
34+
<RedirectError />
35+
<Policies />
36+
</div>
37+
</>
38+
)}
39+
</CardContent>
40+
</Card>
41+
</div>
42+
);
43+
}

0 commit comments

Comments
 (0)