Skip to content

Commit 71ebeaa

Browse files
committed
test: Add comprehensive unit tests for Firebase UI authentication methods
1 parent b918deb commit 71ebeaa

File tree

4 files changed

+590
-3
lines changed

4 files changed

+590
-3
lines changed

packages/firebaseui-core/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
"zod": "^3.24.1"
4343
},
4444
"devDependencies": {
45+
"@types/jsdom": "^21.1.7",
46+
"jsdom": "^26.0.0",
4547
"prettier": "^3.1.1",
4648
"rimraf": "^6.0.1",
4749
"tsup": "^8.0.1",
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import {
3+
Auth,
4+
EmailAuthProvider,
5+
PhoneAuthProvider,
6+
createUserWithEmailAndPassword,
7+
getAuth,
8+
isSignInWithEmailLink,
9+
linkWithCredential,
10+
linkWithRedirect,
11+
sendPasswordResetEmail,
12+
sendSignInLinkToEmail,
13+
signInAnonymously,
14+
signInWithCredential,
15+
signInWithPhoneNumber,
16+
signInWithRedirect,
17+
} from 'firebase/auth';
18+
import {
19+
fuiSignInWithEmailAndPassword,
20+
fuiCreateUserWithEmailAndPassword,
21+
fuiSignInWithPhoneNumber,
22+
fuiConfirmPhoneNumber,
23+
fuiSendPasswordResetEmail,
24+
fuiSendSignInLinkToEmail,
25+
fuiSignInWithEmailLink,
26+
fuiSignInAnonymously,
27+
fuiSignInWithOAuth,
28+
fuiCompleteEmailLinkSignIn,
29+
} from './auth';
30+
import { FirebaseUIError } from './errors';
31+
32+
// Mock all Firebase Auth functions
33+
vi.mock('firebase/auth', async () => {
34+
const actual = await vi.importActual('firebase/auth');
35+
return {
36+
...(actual as object),
37+
getAuth: vi.fn(),
38+
signInWithCredential: vi.fn(),
39+
createUserWithEmailAndPassword: vi.fn(),
40+
signInWithPhoneNumber: vi.fn(),
41+
sendPasswordResetEmail: vi.fn(),
42+
sendSignInLinkToEmail: vi.fn(),
43+
isSignInWithEmailLink: vi.fn(),
44+
signInAnonymously: vi.fn(),
45+
linkWithCredential: vi.fn(),
46+
linkWithRedirect: vi.fn(),
47+
signInWithRedirect: vi.fn(),
48+
EmailAuthProvider: {
49+
credential: vi.fn(),
50+
credentialWithLink: vi.fn(),
51+
},
52+
PhoneAuthProvider: {
53+
credential: vi.fn(),
54+
},
55+
};
56+
});
57+
58+
describe('Firebase UI Auth', () => {
59+
let mockAuth: Auth;
60+
61+
const mockCredential = { type: 'password', token: 'mock-token' };
62+
const mockUserCredential = { user: { uid: 'mock-uid' } };
63+
const mockConfirmationResult = { verificationId: 'mock-verification-id' };
64+
const mockError = { name: 'FirebaseError', code: 'auth/user-not-found' };
65+
const mockProvider = { providerId: 'google.com' };
66+
67+
beforeEach(() => {
68+
vi.clearAllMocks();
69+
// Create a fresh mock auth instance for each test
70+
mockAuth = { currentUser: null } as Auth;
71+
// Reset localStorage
72+
window.localStorage.clear();
73+
// Mock EmailAuthProvider
74+
(EmailAuthProvider.credential as any).mockReturnValue(mockCredential);
75+
(EmailAuthProvider.credentialWithLink as any).mockReturnValue(mockCredential);
76+
// Mock PhoneAuthProvider
77+
(PhoneAuthProvider.credential as any).mockReturnValue(mockCredential);
78+
});
79+
80+
describe('fuiSignInWithEmailAndPassword', () => {
81+
it('should sign in with email and password', async () => {
82+
(signInWithCredential as any).mockResolvedValue(mockUserCredential);
83+
84+
const result = await fuiSignInWithEmailAndPassword(mockAuth, '[email protected]', 'password');
85+
86+
expect(EmailAuthProvider.credential).toHaveBeenCalledWith('[email protected]', 'password');
87+
expect(signInWithCredential).toHaveBeenCalledWith(mockAuth, mockCredential);
88+
expect(result).toBe(mockUserCredential);
89+
});
90+
91+
it('should upgrade anonymous user when enabled', async () => {
92+
mockAuth = { currentUser: { isAnonymous: true } } as Auth;
93+
(linkWithCredential as any).mockResolvedValue(mockUserCredential);
94+
95+
const result = await fuiSignInWithEmailAndPassword(mockAuth, '[email protected]', 'password', {
96+
enableAutoUpgradeAnonymous: true,
97+
});
98+
99+
expect(linkWithCredential).toHaveBeenCalledWith(mockAuth.currentUser, mockCredential);
100+
expect(result).toBe(mockUserCredential);
101+
});
102+
103+
it('should throw FirebaseUIError on error', async () => {
104+
(signInWithCredential as any).mockRejectedValue(mockError);
105+
106+
await expect(fuiSignInWithEmailAndPassword(mockAuth, '[email protected]', 'password')).rejects.toBeInstanceOf(
107+
FirebaseUIError
108+
);
109+
});
110+
});
111+
112+
describe('fuiCreateUserWithEmailAndPassword', () => {
113+
it('should create user with email and password', async () => {
114+
(createUserWithEmailAndPassword as any).mockResolvedValue(mockUserCredential);
115+
116+
const result = await fuiCreateUserWithEmailAndPassword(mockAuth, '[email protected]', 'password');
117+
118+
expect(createUserWithEmailAndPassword).toHaveBeenCalledWith(mockAuth, '[email protected]', 'password');
119+
expect(result).toBe(mockUserCredential);
120+
});
121+
122+
it('should upgrade anonymous user when enabled', async () => {
123+
mockAuth = { currentUser: { isAnonymous: true } } as Auth;
124+
(linkWithCredential as any).mockResolvedValue(mockUserCredential);
125+
126+
const result = await fuiCreateUserWithEmailAndPassword(mockAuth, '[email protected]', 'password', {
127+
enableAutoUpgradeAnonymous: true,
128+
});
129+
130+
expect(linkWithCredential).toHaveBeenCalledWith(mockAuth.currentUser, mockCredential);
131+
expect(result).toBe(mockUserCredential);
132+
});
133+
});
134+
135+
describe('fuiSignInWithPhoneNumber', () => {
136+
it('should initiate phone number sign in', async () => {
137+
(signInWithPhoneNumber as any).mockResolvedValue(mockConfirmationResult);
138+
const mockRecaptcha = { type: 'recaptcha' };
139+
140+
const result = await fuiSignInWithPhoneNumber(mockAuth, '+1234567890', mockRecaptcha as any);
141+
142+
expect(signInWithPhoneNumber).toHaveBeenCalledWith(mockAuth, '+1234567890', mockRecaptcha);
143+
expect(result).toBe(mockConfirmationResult);
144+
});
145+
});
146+
147+
describe('fuiConfirmPhoneNumber', () => {
148+
it('should confirm phone number sign in', async () => {
149+
(getAuth as any).mockReturnValue(mockAuth);
150+
(signInWithCredential as any).mockResolvedValue(mockUserCredential);
151+
152+
const result = await fuiConfirmPhoneNumber({ verificationId: 'mock-id' } as any, '123456');
153+
154+
expect(PhoneAuthProvider.credential).toHaveBeenCalledWith('mock-id', '123456');
155+
expect(signInWithCredential).toHaveBeenCalledWith(mockAuth, mockCredential);
156+
expect(result).toBe(mockUserCredential);
157+
});
158+
159+
it('should upgrade anonymous user when enabled', async () => {
160+
const mockAuthWithAnonymousUser = { currentUser: { isAnonymous: true } } as Auth;
161+
(getAuth as any).mockReturnValue(mockAuthWithAnonymousUser);
162+
(linkWithCredential as any).mockResolvedValue(mockUserCredential);
163+
164+
const result = await fuiConfirmPhoneNumber({ verificationId: 'mock-id' } as any, '123456', {
165+
enableAutoUpgradeAnonymous: true,
166+
});
167+
168+
expect(linkWithCredential).toHaveBeenCalled();
169+
expect(result).toBe(mockUserCredential);
170+
});
171+
});
172+
173+
describe('fuiSendPasswordResetEmail', () => {
174+
it('should send password reset email', async () => {
175+
(sendPasswordResetEmail as any).mockResolvedValue(undefined);
176+
177+
await fuiSendPasswordResetEmail(mockAuth, '[email protected]');
178+
179+
expect(sendPasswordResetEmail).toHaveBeenCalledWith(mockAuth, '[email protected]');
180+
});
181+
});
182+
183+
describe('fuiSendSignInLinkToEmail', () => {
184+
it('should send sign in link to email', async () => {
185+
(sendSignInLinkToEmail as any).mockResolvedValue(undefined);
186+
const expectedActionCodeSettings = {
187+
url: window.location.href,
188+
handleCodeInApp: true,
189+
};
190+
191+
await fuiSendSignInLinkToEmail(mockAuth, '[email protected]');
192+
193+
expect(sendSignInLinkToEmail).toHaveBeenCalledWith(mockAuth, '[email protected]', expectedActionCodeSettings);
194+
expect(window.localStorage.getItem('emailForSignIn')).toBe('[email protected]');
195+
});
196+
197+
it('should handle anonymous upgrade flag', async () => {
198+
mockAuth = { currentUser: { isAnonymous: true } } as Auth;
199+
(sendSignInLinkToEmail as any).mockResolvedValue(undefined);
200+
201+
await fuiSendSignInLinkToEmail(mockAuth, '[email protected]', { enableAutoUpgradeAnonymous: true });
202+
203+
expect(window.localStorage.getItem('emailLinkAnonymousUpgrade')).toBe('true');
204+
});
205+
});
206+
207+
describe('fuiSignInWithEmailLink', () => {
208+
it('should sign in with email link', async () => {
209+
(signInWithCredential as any).mockResolvedValue(mockUserCredential);
210+
211+
const result = await fuiSignInWithEmailLink(mockAuth, '[email protected]', 'mock-link');
212+
213+
expect(EmailAuthProvider.credentialWithLink).toHaveBeenCalledWith('[email protected]', 'mock-link');
214+
expect(signInWithCredential).toHaveBeenCalledWith(mockAuth, mockCredential);
215+
expect(result).toBe(mockUserCredential);
216+
});
217+
218+
it('should upgrade anonymous user when enabled', async () => {
219+
mockAuth = { currentUser: { isAnonymous: true } } as Auth;
220+
window.localStorage.setItem('emailLinkAnonymousUpgrade', 'true');
221+
(linkWithCredential as any).mockResolvedValue(mockUserCredential);
222+
223+
const result = await fuiSignInWithEmailLink(mockAuth, '[email protected]', 'mock-link', {
224+
enableAutoUpgradeAnonymous: true,
225+
});
226+
227+
expect(linkWithCredential).toHaveBeenCalled();
228+
expect(result).toBe(mockUserCredential);
229+
expect(window.localStorage.getItem('emailLinkAnonymousUpgrade')).toBeNull();
230+
});
231+
});
232+
233+
describe('fuiSignInAnonymously', () => {
234+
it('should sign in anonymously', async () => {
235+
(signInAnonymously as any).mockResolvedValue(mockUserCredential);
236+
237+
const result = await fuiSignInAnonymously(mockAuth);
238+
239+
expect(signInAnonymously).toHaveBeenCalledWith(mockAuth);
240+
expect(result).toBe(mockUserCredential);
241+
});
242+
});
243+
244+
describe('fuiSignInWithOAuth', () => {
245+
it('should sign in with OAuth provider', async () => {
246+
(signInWithRedirect as any).mockResolvedValue(undefined);
247+
248+
await fuiSignInWithOAuth(mockAuth, mockProvider as any);
249+
250+
expect(signInWithRedirect).toHaveBeenCalledWith(mockAuth, mockProvider);
251+
});
252+
253+
it('should upgrade anonymous user when enabled', async () => {
254+
mockAuth = { currentUser: { isAnonymous: true } } as Auth;
255+
(linkWithRedirect as any).mockResolvedValue(undefined);
256+
257+
await fuiSignInWithOAuth(mockAuth, mockProvider as any, { enableAutoUpgradeAnonymous: true });
258+
259+
expect(linkWithRedirect).toHaveBeenCalledWith(mockAuth.currentUser, mockProvider);
260+
});
261+
});
262+
263+
describe('fuiCompleteEmailLinkSignIn', () => {
264+
it('should complete email link sign in when valid', async () => {
265+
(isSignInWithEmailLink as any).mockReturnValue(true);
266+
window.localStorage.setItem('emailForSignIn', '[email protected]');
267+
(signInWithCredential as any).mockResolvedValue(mockUserCredential);
268+
269+
const result = await fuiCompleteEmailLinkSignIn(mockAuth, 'mock-url');
270+
271+
expect(result).toBe(mockUserCredential);
272+
expect(window.localStorage.getItem('emailForSignIn')).toBeNull();
273+
});
274+
275+
it('should return null when not a valid sign in link', async () => {
276+
(isSignInWithEmailLink as any).mockReturnValue(false);
277+
278+
const result = await fuiCompleteEmailLinkSignIn(mockAuth, 'mock-url');
279+
280+
expect(result).toBeNull();
281+
});
282+
283+
it('should return null when no email in storage', async () => {
284+
(isSignInWithEmailLink as any).mockReturnValue(true);
285+
286+
const result = await fuiCompleteEmailLinkSignIn(mockAuth, 'mock-url');
287+
288+
expect(result).toBeNull();
289+
});
290+
});
291+
});

packages/firebaseui-core/vitest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { defineConfig } from 'vitest/config';
33
export default defineConfig({
44
test: {
55
// Use the same environment as the package
6-
environment: 'node',
6+
environment: 'jsdom',
77
// Include TypeScript files
88
include: ['src/**/*.{test,spec}.{js,ts}'],
99
// Exclude build output and node_modules

0 commit comments

Comments
 (0)