4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
6
6
* You may obtain a copy of the License at
7
- *
8
7
* http://www.apache.org/licenses/LICENSE-2.0
9
8
*
10
9
* Unless required by applicable law or agreed to in writing, software
14
13
* limitations under the License.
15
14
*/
16
15
17
- import { describe , it , expect , vi , beforeEach } from "vitest" ;
18
- import { render , screen , fireEvent , waitFor } from "@testing-library/react" ;
19
- import "@testing-library/jest-dom" ;
20
- import { OAuthButton } from "../../../../src/auth/oauth/oauth-button" ;
16
+ import { describe , it , expect , vi , afterEach , beforeEach } from "vitest" ;
17
+ import { render , screen , fireEvent , cleanup } from "@testing-library/react" ;
18
+ import { OAuthButton } from "./oauth-button" ;
19
+ import { CreateFirebaseUIProvider , createMockUI } from "~/tests/utils" ;
20
+ import { registerLocale } from "@firebase-ui/translations" ;
21
21
import type { AuthProvider } from "firebase/auth" ;
22
- import { signInWithOAuth } from "@firebase-ui/core" ;
22
+ import { ComponentProps } from "react" ;
23
+
24
+ import { signInWithProvider } from "@firebase-ui/core" ;
23
25
24
- // Mock signInWithOAuth function
25
- vi . mock ( "@firebase-ui/core" , async ( importOriginal ) => {
26
- const mod = await importOriginal < typeof import ( "@firebase-ui/core" ) > ( ) ;
26
+ vi . mock ( '@firebase-ui/core' , async ( importOriginal ) => {
27
+ const mod = await importOriginal ( ) ;
27
28
return {
28
- ...mod ,
29
- signInWithOAuth : vi . fn ( ) ,
29
+ ...( mod as object ) ,
30
+ signInWithProvider : vi . fn ( ) ,
31
+ FirebaseUIError : class FirebaseUIError extends Error {
32
+ code : string ;
33
+ constructor ( error : any , _ui : any ) {
34
+ const errorCode = error ?. code || "unknown" ;
35
+ const message = errorCode === "auth/user-not-found"
36
+ ? "No account found with this email address"
37
+ : errorCode === "auth/wrong-password"
38
+ ? "The password is invalid or the user does not have a password"
39
+ : "An unexpected error occurred" ;
40
+ super ( message ) ;
41
+ this . name = "FirebaseUIError" ;
42
+ this . code = errorCode ;
43
+ }
44
+ } ,
30
45
} ;
31
46
} ) ;
32
47
33
- // Create a mock provider that matches the AuthProvider interface
34
- const mockGoogleProvider = { providerId : "google.com" } as AuthProvider ;
35
-
36
- // Mock React hooks from the package
37
- const useAuthMock = vi . fn ( ) ;
48
+ vi . mock ( "~/components/button" , async ( importOriginal ) => {
49
+ const mod = await importOriginal < typeof import ( "~/components/button" ) > ( ) ;
50
+ return {
51
+ ...mod ,
52
+ Button : ( props : ComponentProps < "button" > ) => (
53
+ < mod . Button data-testid = "oauth-button" { ...props } />
54
+ ) ,
55
+ }
56
+ } ) ;
38
57
39
- vi . mock ( "../../../../src/hooks" , ( ) => ( {
40
- useAuth : ( ) => useAuthMock ( ) ,
41
- useUI : ( ) => vi . fn ( ) ,
42
- } ) ) ;
58
+ afterEach ( ( ) => {
59
+ cleanup ( ) ;
60
+ } ) ;
43
61
44
- // Mock the Button component
45
- vi . mock ( "../../../../src/components/button" , ( ) => ( {
46
- Button : ( { children, onClick, disabled } : any ) => (
47
- < button onClick = { onClick } disabled = { disabled } data-testid = "oauth-button" >
48
- { children }
49
- </ button >
50
- ) ,
51
- } ) ) ;
62
+ describe ( "<OAuthButton />" , ( ) => {
63
+ const mockGoogleProvider = { providerId : "google.com" } as AuthProvider ;
52
64
53
- describe ( "OAuthButton Component" , ( ) => {
54
65
beforeEach ( ( ) => {
55
66
vi . clearAllMocks ( ) ;
56
67
} ) ;
57
68
58
69
it ( "renders a button with the provided children" , ( ) => {
59
- render ( < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton > ) ;
70
+ const ui = createMockUI ( ) ;
71
+
72
+ render (
73
+ < CreateFirebaseUIProvider ui = { ui } >
74
+ < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton >
75
+ </ CreateFirebaseUIProvider >
76
+ ) ;
77
+
78
+ const button = screen . getByTestId ( "oauth-button" ) ;
79
+ expect ( button ) . toBeDefined ( ) ;
80
+ expect ( button . textContent ) . toBe ( "Sign in with Google" ) ;
81
+ } ) ;
82
+
83
+ it ( "applies correct CSS classes" , ( ) => {
84
+ const ui = createMockUI ( ) ;
85
+
86
+ render (
87
+ < CreateFirebaseUIProvider ui = { ui } >
88
+ < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton >
89
+ </ CreateFirebaseUIProvider >
90
+ ) ;
60
91
61
92
const button = screen . getByTestId ( "oauth-button" ) ;
62
- expect ( button ) . toBeInTheDocument ( ) ;
63
- expect ( button ) . toHaveTextContent ( "Sign in with Google ") ;
93
+ expect ( button . className ) . toContain ( "fui-provider__button" ) ;
94
+ expect ( button . getAttribute ( "type" ) ) . toBe ( "button ") ;
64
95
} ) ;
65
96
66
- // TODO: Fix this test
67
- it . skip ( "calls signInWithOAuth when clicked" , async ( ) => {
68
- // Mock the signInWithOAuth to resolve immediately
69
- vi . mocked ( signInWithOAuth ) . mockResolvedValueOnce ( undefined ) ;
97
+ it ( "is disabled when UI state is not idle" , ( ) => {
98
+ const ui = createMockUI ( ) ;
99
+ ui . setKey ( "state" , "pending" ) ;
70
100
71
- render ( < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton > ) ;
101
+ render (
102
+ < CreateFirebaseUIProvider ui = { ui } >
103
+ < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton >
104
+ </ CreateFirebaseUIProvider >
105
+ ) ;
106
+
107
+ const button = screen . getByTestId ( "oauth-button" ) ;
108
+ expect ( button ) . toHaveAttribute ( "disabled" ) ;
109
+ } ) ;
110
+
111
+ it ( "is enabled when UI state is idle" , ( ) => {
112
+ const ui = createMockUI ( ) ;
113
+
114
+ render (
115
+ < CreateFirebaseUIProvider ui = { ui } >
116
+ < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton >
117
+ </ CreateFirebaseUIProvider >
118
+ ) ;
119
+
120
+ const button = screen . getByTestId ( "oauth-button" ) ;
121
+ expect ( button ) . not . toHaveAttribute ( "disabled" ) ;
122
+ } ) ;
123
+
124
+ it ( "calls signInWithProvider when clicked" , async ( ) => {
125
+ const mockSignInWithProvider = vi . mocked ( signInWithProvider ) ;
126
+ mockSignInWithProvider . mockResolvedValue ( undefined ) ;
127
+
128
+ const ui = createMockUI ( ) ;
129
+
130
+ render (
131
+ < CreateFirebaseUIProvider ui = { ui } >
132
+ < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton >
133
+ </ CreateFirebaseUIProvider >
134
+ ) ;
72
135
73
136
const button = screen . getByTestId ( "oauth-button" ) ;
74
137
fireEvent . click ( button ) ;
75
138
76
- await waitFor ( ( ) => {
77
- expect ( signInWithOAuth ) . toHaveBeenCalledTimes ( 1 ) ;
78
- expect ( signInWithOAuth ) . toHaveBeenCalledWith ( expect . anything ( ) , mockGoogleProvider ) ;
79
- } ) ;
139
+ expect ( mockSignInWithProvider ) . toHaveBeenCalledTimes ( 1 ) ;
140
+ expect ( mockSignInWithProvider ) . toHaveBeenCalledWith ( expect . anything ( ) , mockGoogleProvider ) ;
80
141
} ) ;
81
142
82
- // TODO: Fix this test
83
- it . skip ( "displays error message when non-Firebase error occurs" , async ( ) => {
143
+ it ( "displays FirebaseUIError message when FirebaseUIError occurs" , async ( ) => {
144
+ const { FirebaseUIError } = await import ( "@firebase-ui/core" ) ;
145
+ const mockSignInWithProvider = vi . mocked ( signInWithProvider ) ;
146
+ const ui = createMockUI ( ) ;
147
+ const mockError = new FirebaseUIError ( { code : "auth/user-not-found" } , ui . get ( ) ) ;
148
+ mockSignInWithProvider . mockRejectedValue ( mockError ) ;
149
+
150
+ render (
151
+ < CreateFirebaseUIProvider ui = { ui } >
152
+ < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton >
153
+ </ CreateFirebaseUIProvider >
154
+ ) ;
155
+
156
+ const button = screen . getByTestId ( "oauth-button" ) ;
157
+ fireEvent . click ( button ) ;
158
+
159
+ // Wait for error to appear
160
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
161
+
162
+ // The error message will be the translated message for auth/user-not-found
163
+ const errorMessage = screen . getByText ( "No account found with this email address" ) ;
164
+ expect ( errorMessage ) . toBeDefined ( ) ;
165
+ expect ( errorMessage . className ) . toContain ( "fui-form__error" ) ;
166
+ } ) ;
167
+
168
+ it ( "displays unknown error message when non-Firebase error occurs" , async ( ) => {
169
+ const mockSignInWithProvider = vi . mocked ( signInWithProvider ) ;
170
+ const regularError = new Error ( "Regular error" ) ;
171
+ mockSignInWithProvider . mockRejectedValue ( regularError ) ;
172
+
84
173
// Mock console.error to prevent test output noise
85
174
const consoleErrorSpy = vi . spyOn ( console , "error" ) . mockImplementation ( ( ) => { } ) ;
86
175
87
- // Mock a non-Firebase error to trigger console.error
88
- const regularError = new Error ( "Regular error" ) ;
89
- vi . mocked ( signInWithOAuth ) . mockRejectedValueOnce ( regularError ) ;
176
+ const ui = createMockUI ( {
177
+ locale : registerLocale ( "test" , {
178
+ errors : {
179
+ unknownError : "unknownError" ,
180
+ } ,
181
+ } ) ,
182
+ } ) ;
90
183
91
- render ( < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton > ) ;
184
+ render (
185
+ < CreateFirebaseUIProvider ui = { ui } >
186
+ < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton >
187
+ </ CreateFirebaseUIProvider >
188
+ ) ;
92
189
93
190
const button = screen . getByTestId ( "oauth-button" ) ;
94
-
95
- // Click the button to trigger the error
96
191
fireEvent . click ( button ) ;
97
192
98
- // Wait for the error message to be displayed
99
- await waitFor ( ( ) => {
100
- // Verify console.error was called with the regular error
101
- expect ( consoleErrorSpy ) . toHaveBeenCalledWith ( regularError ) ;
193
+ // Wait for error to appear
194
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
102
195
103
- // Verify the error message is displayed
104
- const errorMessage = screen . getByText ( "An unknown error occurred" ) ;
105
- expect ( errorMessage ) . toBeInTheDocument ( ) ;
106
- expect ( errorMessage ) . toHaveClass ( "fui-form__error" ) ;
107
- } ) ;
196
+ expect ( consoleErrorSpy ) . toHaveBeenCalledWith ( regularError ) ;
197
+
198
+ const errorMessage = screen . getByText ( "unknownError" ) ;
199
+ expect ( errorMessage ) . toBeDefined ( ) ;
200
+ expect ( errorMessage . className ) . toContain ( "fui-form__error" ) ;
108
201
109
202
// Restore console.error
110
203
consoleErrorSpy . mockRestore ( ) ;
111
204
} ) ;
112
- } ) ;
205
+
206
+ it ( "clears error when button is clicked again" , async ( ) => {
207
+ const { FirebaseUIError } = await import ( "@firebase-ui/core" ) ;
208
+ const mockSignInWithProvider = vi . mocked ( signInWithProvider ) ;
209
+ const ui = createMockUI ( ) ;
210
+
211
+ // First call fails, second call succeeds
212
+ mockSignInWithProvider
213
+ . mockRejectedValueOnce ( new FirebaseUIError ( { code : "auth/wrong-password" } , ui . get ( ) ) )
214
+ . mockResolvedValueOnce ( undefined ) ;
215
+
216
+ render (
217
+ < CreateFirebaseUIProvider ui = { ui } >
218
+ < OAuthButton provider = { mockGoogleProvider } > Sign in with Google</ OAuthButton >
219
+ </ CreateFirebaseUIProvider >
220
+ ) ;
221
+
222
+ const button = screen . getByTestId ( "oauth-button" ) ;
223
+
224
+ // First click - should show error
225
+ fireEvent . click ( button ) ;
226
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
227
+
228
+ // The error message will be the translated message for auth/wrong-password
229
+ const errorMessage = screen . getByText ( "The password is invalid or the user does not have a password" ) ;
230
+ expect ( errorMessage ) . toBeDefined ( ) ;
231
+
232
+ // Second click - should clear error
233
+ fireEvent . click ( button ) ;
234
+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
235
+
236
+ expect ( screen . queryByText ( "The password is invalid or the user does not have a password" ) ) . toBeNull ( ) ;
237
+ } ) ;
238
+ } ) ;
0 commit comments