Skip to content

Commit af5eec2

Browse files
authored
test: Integration tests for React package
2 parents 8518adb + 9c35ebf commit af5eec2

21 files changed

+1289
-121
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: React Integration Tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
integration-test:
11+
runs-on: ubuntu-latest
12+
13+
strategy:
14+
matrix:
15+
node-version: [22.x]
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Use Node.js ${{ matrix.node-version }}
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: ${{ matrix.node-version }}
24+
25+
- name: Setup Java for Firebase Emulator
26+
uses: actions/setup-java@v4
27+
with:
28+
distribution: "temurin"
29+
java-version: "17"
30+
31+
- name: Install pnpm
32+
uses: pnpm/action-setup@v2
33+
with:
34+
version: 8
35+
36+
- name: Install dependencies
37+
run: pnpm install
38+
39+
- name: Build core package
40+
run: pnpm --filter @firebase-ui/core build
41+
42+
- name: Install Firebase Tools
43+
run: npm install -g firebase-tools
44+
45+
- name: Create Firebase config
46+
run: |
47+
cat > firebase.json << EOF
48+
{
49+
"emulators": {
50+
"auth": {
51+
"port": 9099
52+
},
53+
"ui": {
54+
"enabled": false
55+
}
56+
}
57+
}
58+
EOF
59+
60+
- name: Start Firebase Emulator and Run Integration Tests
61+
run: |
62+
firebase emulators:start &
63+
sleep 10
64+
pnpm --filter @firebase-ui/react test:integration

.github/workflows/react-unit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ jobs:
3131
run: pnpm install
3232

3333
- name: Run unit tests
34-
run: pnpm --filter @firebase-ui/react test
34+
run: pnpm --filter @firebase-ui/react test:unit

packages/firebaseui-react/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323
"lint": "tsc --noEmit",
2424
"format": "prettier --write \"src/**/*.ts\"",
2525
"clean": "rimraf dist",
26-
"test:unit": "vitest run tests/unit",
27-
"test:unit:watch": "vitest tests/unit",
28-
"test": "vitest run"
26+
"test:unit": "TEST_TYPE=unit vitest run tests/unit",
27+
"test:unit:watch": "TEST_TYPE=unit vitest tests/unit",
28+
"test:integration": "TEST_TYPE=integration vitest run tests/integration",
29+
"test:integration:watch": "TEST_TYPE=integration vitest tests/integration"
2930
},
3031
"peerDependencies": {
3132
"@firebase-ui/core": "workspace:*",
@@ -54,7 +55,7 @@
5455
"tsup": "^8.3.6",
5556
"typescript": "~5.6.2",
5657
"vite": "^6.0.5",
57-
"vitest": "^3.0.7",
58+
"vitest": "^3.0.8",
5859
"vitest-tsconfig-paths": "^3.4.1"
5960
}
6061
}

packages/firebaseui-react/src/auth/forms/email-link-form.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function EmailLinkForm() {
2424
const translations = useTranslations();
2525
const [formError, setFormError] = useState<string | null>(null);
2626
const [emailSent, setEmailSent] = useState(false);
27+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
2728

2829
const emailLinkFormSchema = useMemo(
2930
() => createEmailLinkFormSchema(translations),
@@ -128,8 +129,17 @@ export function EmailLinkForm() {
128129
name={field.name}
129130
type="email"
130131
value={field.state.value}
131-
onBlur={field.handleBlur}
132-
onChange={(e) => field.handleChange(e.target.value)}
132+
onBlur={() => {
133+
setFirstValidationOccured(true);
134+
field.handleBlur();
135+
}}
136+
onInput={(e) => {
137+
field.handleChange((e.target as HTMLInputElement).value);
138+
if (firstValidationOccured) {
139+
field.handleBlur();
140+
form.update();
141+
}
142+
}}
133143
/>
134144
<FieldInfo field={field} />
135145
</label>

packages/firebaseui-react/src/auth/forms/email-password-form.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export function EmailPasswordForm({
3131
enableHandleExistingCredential,
3232
} = useConfig();
3333
const [formError, setFormError] = useState<string | null>(null);
34+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
3435

3536
// TODO: Do we need to memoize this?
3637
const emailFormSchema = useMemo(
@@ -102,8 +103,17 @@ export function EmailPasswordForm({
102103
name={field.name}
103104
type="email"
104105
value={field.state.value}
105-
onBlur={field.handleBlur}
106-
onChange={(e) => field.handleChange(e.target.value)}
106+
onBlur={() => {
107+
setFirstValidationOccured(true);
108+
field.handleBlur();
109+
}}
110+
onInput={(e) => {
111+
field.handleChange((e.target as HTMLInputElement).value);
112+
if (firstValidationOccured) {
113+
field.handleBlur();
114+
form.update();
115+
}
116+
}}
107117
/>
108118
<FieldInfo field={field} />
109119
</label>
@@ -149,8 +159,17 @@ export function EmailPasswordForm({
149159
name={field.name}
150160
type="password"
151161
value={field.state.value}
152-
onBlur={field.handleBlur}
153-
onChange={(e) => field.handleChange(e.target.value)}
162+
onBlur={() => {
163+
setFirstValidationOccured(true);
164+
field.handleBlur();
165+
}}
166+
onInput={(e) => {
167+
field.handleChange((e.target as HTMLInputElement).value);
168+
if (firstValidationOccured) {
169+
field.handleBlur();
170+
form.update();
171+
}
172+
}}
154173
/>
155174
<FieldInfo field={field} />
156175
</label>

packages/firebaseui-react/src/auth/forms/forgot-password-form.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function ForgotPasswordForm({
2626
const { language } = useConfig();
2727
const [formError, setFormError] = useState<string | null>(null);
2828
const [emailSent, setEmailSent] = useState(false);
29+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
2930
const forgotPasswordFormSchema = useMemo(
3031
() => createForgotPasswordFormSchema(translations),
3132
[translations]
@@ -106,8 +107,17 @@ export function ForgotPasswordForm({
106107
name={field.name}
107108
type="email"
108109
value={field.state.value}
109-
onBlur={field.handleBlur}
110-
onChange={(e) => field.handleChange(e.target.value)}
110+
onBlur={() => {
111+
setFirstValidationOccured(true);
112+
field.handleBlur();
113+
}}
114+
onInput={(e) => {
115+
field.handleChange((e.target as HTMLInputElement).value);
116+
if (firstValidationOccured) {
117+
field.handleBlur();
118+
form.update();
119+
}
120+
}}
111121
/>
112122
<FieldInfo field={field} />
113123
</label>

packages/firebaseui-react/src/auth/forms/phone-form.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ function PhoneNumberForm({
4040
const [selectedCountry, setSelectedCountry] = useState<CountryData>(
4141
countryData[0]
4242
);
43+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
4344

4445
const phoneFormSchema = useMemo(
4546
() =>
@@ -104,8 +105,17 @@ function PhoneNumberForm({
104105
name={field.name}
105106
type="tel"
106107
value={field.state.value}
107-
onBlur={field.handleBlur}
108-
onChange={(e) => field.handleChange(e.target.value)}
108+
onBlur={() => {
109+
setFirstValidationOccured(true);
110+
field.handleBlur();
111+
}}
112+
onInput={(e) => {
113+
field.handleChange((e.target as HTMLInputElement).value);
114+
if (firstValidationOccured) {
115+
field.handleBlur();
116+
phoneForm.update();
117+
}
118+
}}
109119
className="fui-phone-input__number-input"
110120
/>
111121
</div>
@@ -195,6 +205,7 @@ function VerificationForm({
195205
}: VerificationFormProps) {
196206
const { language } = useConfig();
197207
const translations = useTranslations();
208+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
198209

199210
const verificationFormSchema = useMemo(
200211
() =>
@@ -249,8 +260,17 @@ function VerificationForm({
249260
name={field.name}
250261
type="text"
251262
value={field.state.value}
252-
onBlur={field.handleBlur}
253-
onChange={(e) => field.handleChange(e.target.value)}
263+
onBlur={() => {
264+
setFirstValidationOccured(true);
265+
field.handleBlur();
266+
}}
267+
onInput={(e) => {
268+
field.handleChange((e.target as HTMLInputElement).value);
269+
if (firstValidationOccured) {
270+
field.handleBlur();
271+
verificationForm.update();
272+
}
273+
}}
254274
/>
255275
<FieldInfo field={field} />
256276
</label>

packages/firebaseui-react/src/auth/forms/register-form.tsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export function RegisterForm({
2323
const { language, enableAutoUpgradeAnonymous } = useConfig();
2424
const translations = useTranslations();
2525
const [formError, setFormError] = useState<string | null>(null);
26+
const [firstValidationOccured, setFirstValidationOccured] = useState(false);
2627
const emailFormSchema = useMemo(
2728
() => createEmailFormSchema(translations),
2829
[translations]
@@ -96,8 +97,17 @@ export function RegisterForm({
9697
name={field.name}
9798
type="email"
9899
value={field.state.value}
99-
onBlur={field.handleBlur}
100-
onChange={(e) => field.handleChange(e.target.value)}
100+
onBlur={() => {
101+
setFirstValidationOccured(true);
102+
field.handleBlur();
103+
}}
104+
onInput={(e) => {
105+
field.handleChange((e.target as HTMLInputElement).value);
106+
if (firstValidationOccured) {
107+
field.handleBlur();
108+
form.update();
109+
}
110+
}}
101111
/>
102112
<FieldInfo field={field} />
103113
</label>
@@ -124,8 +134,17 @@ export function RegisterForm({
124134
name={field.name}
125135
type="password"
126136
value={field.state.value}
127-
onBlur={field.handleBlur}
128-
onChange={(e) => field.handleChange(e.target.value)}
137+
onBlur={() => {
138+
setFirstValidationOccured(true);
139+
field.handleBlur();
140+
}}
141+
onInput={(e) => {
142+
field.handleChange((e.target as HTMLInputElement).value);
143+
if (firstValidationOccured) {
144+
field.handleBlur();
145+
form.update();
146+
}
147+
}}
129148
/>
130149
<FieldInfo field={field} />
131150
</label>
Lines changed: 36 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,36 @@
1-
import { vi } from "vitest";
2-
3-
// Define TranslationStrings type to match core package
4-
export type TranslationStrings = Record<string, string>;
5-
6-
// Implement FirebaseUIError with the same interface as the real implementation
7-
export class FirebaseUIError extends Error {
8-
code: string;
9-
constructor(
10-
error: any,
11-
_translations?: Partial<Record<string, Partial<TranslationStrings>>>,
12-
_language?: string
13-
) {
14-
// Extract error code from the error object
15-
const errorCode =
16-
typeof error === "string" ? error : error?.code || "unknown";
17-
18-
// For simplicity in tests, we'll use a direct message if provided as a string
19-
// or extract from translations if provided
20-
let errorMessage = `Error: ${errorCode}`;
21-
22-
if (
23-
typeof error === "string" &&
24-
arguments.length > 1 &&
25-
typeof arguments[1] === "string"
26-
) {
27-
// Handle case where first arg is code and second is message (for test convenience)
28-
errorMessage = arguments[1];
29-
}
30-
31-
super(errorMessage);
32-
this.name = "FirebaseUIError";
33-
this.code = errorCode;
34-
}
35-
}
36-
37-
// Authentication functions
38-
export const fuiSignInWithEmailAndPassword = vi.fn();
39-
export const fuiSignInWithEmailLink = vi.fn();
40-
export const fuiSignInWithPhone = vi.fn();
41-
export const fuiSignInWithOAuth = vi.fn();
42-
export const fuiResetPassword = vi.fn();
43-
export const fuiCreateUserWithEmailAndPassword = vi.fn();
44-
45-
// Country data for phone authentication
46-
export const countryData = [
47-
{ code: "US", name: "United States", dialCode: "+1", emoji: "🇺🇸" },
48-
{ code: "GB", name: "United Kingdom", dialCode: "+44", emoji: "🇬🇧" },
49-
{ code: "DE", name: "Germany", dialCode: "+49", emoji: "🇩🇪" },
50-
{ code: "FR", name: "France", dialCode: "+33", emoji: "🇫🇷" },
51-
{ code: "JP", name: "Japan", dialCode: "+81", emoji: "🇯🇵" },
52-
];
53-
54-
// Translation helpers
55-
export const getTranslation = vi.fn((section, key) => `${section}.${key}`);
1+
/**
2+
* This is the automatic module mock for @firebase-ui/core
3+
* It re-exports the mock implementations from utils/mocks.ts to avoid duplication
4+
*/
5+
import {
6+
FirebaseUIError,
7+
TranslationStrings,
8+
mockCountryData,
9+
createCoreMocks,
10+
} from "../../utils/mocks";
11+
12+
// Create a plain object with all the mocks
13+
const coreMocks = createCoreMocks();
14+
15+
// Export types
16+
export type { TranslationStrings };
17+
18+
// Export the error class
19+
export { FirebaseUIError };
20+
21+
// Export other values
22+
export const countryData = mockCountryData;
23+
24+
// Export mock functions directly from coreMocks
25+
export const fuiSignInWithEmailAndPassword =
26+
coreMocks.fuiSignInWithEmailAndPassword;
27+
export const fuiSignInWithEmailLink = coreMocks.fuiSignInWithEmailLink;
28+
export const fuiSendSignInLinkToEmail = coreMocks.fuiSendSignInLinkToEmail;
29+
export const fuiCompleteEmailLinkSignIn = coreMocks.fuiCompleteEmailLinkSignIn;
30+
export const fuiSignInWithPhone = coreMocks.fuiSignInWithPhone;
31+
export const fuiSignInWithOAuth = coreMocks.fuiSignInWithOAuth;
32+
export const fuiResetPassword = coreMocks.fuiResetPassword;
33+
export const fuiSendPasswordResetEmail = coreMocks.fuiSendPasswordResetEmail;
34+
export const fuiCreateUserWithEmailAndPassword =
35+
coreMocks.fuiCreateUserWithEmailAndPassword;
36+
export const getTranslation = coreMocks.getTranslation;

0 commit comments

Comments
 (0)