11// packages/clerk-js/src/ui/elements/__tests__/SocialButtons.test.tsx
2- import { render , screen } from '@testing-library/react' ;
2+ import { render , renderHook , screen } from '@testing-library/react' ;
33import { describe , expect , it , vi } from 'vitest' ;
44
55import { bindCreateFixtures } from '@/test/utils' ;
66import { CardStateProvider } from '@/ui/elements/contexts' ;
7+ import { useTotalEnabledAuthMethods } from '@/ui/hooks/useTotalEnabledAuthMethods' ;
78
89import { SocialButtons } from '../SocialButtons' ;
910
@@ -117,9 +118,101 @@ describe('SocialButtons', () => {
117118 } ) ;
118119
119120 describe ( 'With last authentication strategy' , ( ) => {
120- it ( 'should show "Continue with" prefix for single strategy' , async ( ) => {
121+ it ( 'should NOT show "Last used" badge when only one total auth method exists (single social provider, no email/username)' , async ( ) => {
122+ const { wrapper, fixtures } = await createFixtures ( f => {
123+ f . withSocialProvider ( { provider : 'google' } ) ;
124+ // Explicitly disable email and username to ensure only one auth method
125+ // Note: By default fixtures have these disabled, but we set them explicitly to be sure
126+ f . withEmailAddress ( { enabled : false , used_for_first_factor : false } ) ;
127+ f . withUsername ( { enabled : false , used_for_first_factor : false } ) ;
128+ } ) ;
129+
130+ // Verify that email and username are disabled
131+ const enabledIdentifiers = fixtures . environment . userSettings . enabledFirstFactorIdentifiers ;
132+ expect ( enabledIdentifiers ) . not . toContain ( 'email_address' ) ;
133+ expect ( enabledIdentifiers ) . not . toContain ( 'username' ) ;
134+ // Verify only one social provider is enabled
135+ expect ( fixtures . environment . userSettings . authenticatableSocialStrategies ) . toHaveLength ( 1 ) ;
136+
137+ // Verify the total count is actually 1 using the hook
138+ const { result } = renderHook ( ( ) => useTotalEnabledAuthMethods ( ) , { wrapper } ) ;
139+ expect ( result . current ) . toBe ( 1 ) ;
140+
141+ fixtures . clerk . client . lastAuthenticationStrategy = 'oauth_google' ;
142+
143+ render (
144+ < CardStateProvider >
145+ < SocialButtons
146+ { ...defaultProps }
147+ showLastAuthenticationStrategy
148+ />
149+ </ CardStateProvider > ,
150+ { wrapper } ,
151+ ) ;
152+
153+ const button = screen . getByRole ( 'button' , { name : / g o o g l e / i } ) ;
154+ expect ( button ) . toHaveTextContent ( 'Continue with Google' ) ;
155+ expect ( button ) . not . toHaveTextContent ( 'Last used' ) ;
156+ expect ( screen . queryByText ( 'Last used' ) ) . not . toBeInTheDocument ( ) ;
157+ } ) ;
158+
159+ it ( 'should show "Last used" badge when email is enabled even with single social provider' , async ( ) => {
160+ const { wrapper, fixtures } = await createFixtures ( f => {
161+ f . withSocialProvider ( { provider : 'google' } ) ;
162+ f . withEmailAddress ( { enabled : true , used_for_first_factor : true } ) ;
163+ f . withUsername ( { enabled : false , used_for_first_factor : false } ) ;
164+ } ) ;
165+
166+ // Verify the total count is 2 (email + google)
167+ const { result } = renderHook ( ( ) => useTotalEnabledAuthMethods ( ) , { wrapper } ) ;
168+ expect ( result . current ) . toBe ( 2 ) ;
169+
170+ fixtures . clerk . client . lastAuthenticationStrategy = 'oauth_google' ;
171+
172+ render (
173+ < CardStateProvider >
174+ < SocialButtons
175+ { ...defaultProps }
176+ showLastAuthenticationStrategy
177+ />
178+ </ CardStateProvider > ,
179+ { wrapper } ,
180+ ) ;
181+
182+ const button = screen . getByRole ( 'button' , { name : / g o o g l e / i } ) ;
183+ expect ( button ) . toHaveTextContent ( 'Continue with Google' ) ;
184+ expect ( button ) . toHaveTextContent ( 'Last used' ) ;
185+ expect ( screen . getByText ( 'Last used' ) ) . toBeInTheDocument ( ) ;
186+ } ) ;
187+
188+ it ( 'should show "Last used" badge when only one social provider but email/username is also enabled' , async ( ) => {
121189 const { wrapper, fixtures } = await createFixtures ( f => {
122190 f . withSocialProvider ( { provider : 'google' } ) ;
191+ f . withEmailAddress ( { enabled : true , used_for_first_factor : true } ) ;
192+ } ) ;
193+
194+ fixtures . clerk . client . lastAuthenticationStrategy = 'oauth_google' ;
195+
196+ render (
197+ < CardStateProvider >
198+ < SocialButtons
199+ { ...defaultProps }
200+ showLastAuthenticationStrategy
201+ />
202+ </ CardStateProvider > ,
203+ { wrapper } ,
204+ ) ;
205+
206+ const button = screen . getByRole ( 'button' , { name : / g o o g l e / i } ) ;
207+ expect ( button ) . toHaveTextContent ( 'Continue with Google' ) ;
208+ expect ( button ) . toHaveTextContent ( 'Last used' ) ;
209+ expect ( screen . getByText ( 'Last used' ) ) . toBeInTheDocument ( ) ;
210+ } ) ;
211+
212+ it ( 'should show "Continue with" prefix for single strategy when multiple auth methods exist' , async ( ) => {
213+ const { wrapper, fixtures } = await createFixtures ( f => {
214+ f . withSocialProvider ( { provider : 'google' } ) ;
215+ f . withEmailAddress ( { enabled : true , used_for_first_factor : true } ) ;
123216 } ) ;
124217
125218 fixtures . clerk . client . lastAuthenticationStrategy = 'oauth_google' ;
@@ -217,6 +310,7 @@ describe('SocialButtons', () => {
217310 it ( 'should handle SAML strategies converted to OAuth' , async ( ) => {
218311 const { wrapper, fixtures } = await createFixtures ( f => {
219312 f . withSocialProvider ( { provider : 'google' } ) ;
313+ f . withEmailAddress ( { enabled : true , used_for_first_factor : true } ) ;
220314 } ) ;
221315
222316 // SAML strategy should be converted to OAuth
@@ -235,5 +329,32 @@ describe('SocialButtons', () => {
235329 const googleButton = screen . getByRole ( 'button' , { name : / g o o g l e / i } ) ;
236330 expect ( googleButton ) . toHaveTextContent ( 'Continue with Google' ) ;
237331 } ) ;
332+
333+ it ( 'should NOT show "Last used" badge when only one total auth method exists (single social provider, no email/username, SAML)' , async ( ) => {
334+ const { wrapper, fixtures } = await createFixtures ( f => {
335+ f . withSocialProvider ( { provider : 'google' } ) ;
336+ // Disable email and username to ensure only one auth method
337+ f . withEmailAddress ( { enabled : false , used_for_first_factor : false } ) ;
338+ f . withUsername ( { enabled : false , used_for_first_factor : false } ) ;
339+ } ) ;
340+
341+ // SAML strategy should be converted to OAuth but badge should not show
342+ fixtures . clerk . client . lastAuthenticationStrategy = 'saml_google' as any ;
343+
344+ render (
345+ < CardStateProvider >
346+ < SocialButtons
347+ { ...defaultProps }
348+ showLastAuthenticationStrategy
349+ />
350+ </ CardStateProvider > ,
351+ { wrapper } ,
352+ ) ;
353+
354+ const googleButton = screen . getByRole ( 'button' , { name : / g o o g l e / i } ) ;
355+ expect ( googleButton ) . toHaveTextContent ( 'Continue with Google' ) ;
356+ expect ( googleButton ) . not . toHaveTextContent ( 'Last used' ) ;
357+ expect ( screen . queryByText ( 'Last used' ) ) . not . toBeInTheDocument ( ) ;
358+ } ) ;
238359 } ) ;
239360} ) ;
0 commit comments