134 lines
4.3 KiB
TypeScript
134 lines
4.3 KiB
TypeScript
import React from 'react'
|
|
import { Alert } from 'react-native'
|
|
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
|
|
|
const mockGetAuthCapabilities = jest.fn()
|
|
const mockSignInWithOAuthProvider = jest.fn()
|
|
const mockNavigate = jest.fn()
|
|
const mockAlert = jest.spyOn(Alert, 'alert').mockImplementation(() => undefined)
|
|
|
|
jest.mock('@react-navigation/native', () => ({
|
|
useNavigation: () => ({ navigate: mockNavigate }),
|
|
}))
|
|
jest.mock('react-native-safe-area-context', () => ({
|
|
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
|
|
}))
|
|
jest.mock('@d3ro/i18n', () => ({
|
|
useI18n: () => ({ t: (key: string) => key }),
|
|
}))
|
|
jest.mock('../src/lib/preferences-context', () => ({
|
|
useMobilePreferences: () => ({
|
|
palette: {
|
|
bg: { app: '#000', card: '#181818', inset: '#202020', cardHover: '#222' },
|
|
text: { primary: '#fff', muted: '#999', onAccent: '#000' },
|
|
border: { default: '#444', strong: '#666' },
|
|
accent: { main: '#fc0', dim: '#221' },
|
|
tag: { green: '#6f6', red: '#f66', orange: '#fa6', blue: '#6af' },
|
|
},
|
|
}),
|
|
}))
|
|
jest.mock('../src/lib/auth-context', () => ({
|
|
useAuth: () => ({ authError: null }),
|
|
}))
|
|
jest.mock('../src/lib/auth-capabilities', () => ({
|
|
getAuthCapabilities: (...args: unknown[]) => mockGetAuthCapabilities(...args),
|
|
}))
|
|
jest.mock('../src/lib/supabase', () => ({
|
|
isSupabaseConfigured: () => true,
|
|
supabase: { auth: { signInWithPassword: jest.fn() } },
|
|
}))
|
|
jest.mock('../src/features/auth/oauth-sign-in', () => {
|
|
class OAuthSignInError extends Error {
|
|
readonly code: 'oauth_url_failed' | 'oauth_callback_failed'
|
|
|
|
constructor(code: 'oauth_url_failed' | 'oauth_callback_failed') {
|
|
super(code)
|
|
this.code = code
|
|
}
|
|
}
|
|
|
|
return {
|
|
OAuthSignInError,
|
|
signInWithOAuthProvider: (...args: unknown[]) => mockSignInWithOAuthProvider(...args),
|
|
}
|
|
})
|
|
|
|
import LoginScreen from '../src/screens/LoginScreen'
|
|
|
|
const ENABLED_CAPABILITIES = {
|
|
signUpEnabled: true,
|
|
emailEnabled: true,
|
|
googleEnabled: true,
|
|
githubEnabled: false,
|
|
appleEnabled: false,
|
|
}
|
|
|
|
async function renderScreen(): Promise<ReactTestRenderer> {
|
|
let renderer: ReactTestRenderer
|
|
await act(async () => {
|
|
renderer = create(<LoginScreen />)
|
|
await Promise.resolve()
|
|
await Promise.resolve()
|
|
})
|
|
return renderer!
|
|
}
|
|
|
|
describe('LoginScreen shared OAuth launcher', () => {
|
|
beforeEach(() => {
|
|
mockGetAuthCapabilities.mockReset().mockResolvedValue(ENABLED_CAPABILITIES)
|
|
mockSignInWithOAuthProvider.mockReset().mockResolvedValue('cancelled')
|
|
mockAlert.mockClear()
|
|
})
|
|
|
|
it('uses the shared launcher and exposes an accessible busy state', async () => {
|
|
let resolveOAuth: ((value: 'cancelled') => void) | null = null
|
|
mockSignInWithOAuthProvider.mockReturnValue(new Promise((resolve) => {
|
|
resolveOAuth = resolve
|
|
}))
|
|
const renderer = await renderScreen()
|
|
|
|
await act(async () => {
|
|
renderer.root.findByProps({ testID: 'oauth-google' }).props.onPress()
|
|
await Promise.resolve()
|
|
})
|
|
|
|
expect(mockSignInWithOAuthProvider).toHaveBeenCalledWith('google')
|
|
expect(renderer.root.findByProps({ testID: 'oauth-google' }).props.accessibilityState)
|
|
.toEqual({ disabled: true, busy: true })
|
|
expect(renderer.root.findByProps({ testID: 'oauth-google-loading' })).toBeTruthy()
|
|
|
|
await act(async () => {
|
|
resolveOAuth?.('cancelled')
|
|
await Promise.resolve()
|
|
})
|
|
|
|
expect(mockAlert).not.toHaveBeenCalled()
|
|
expect(renderer.root.findByProps({ testID: 'oauth-google' }).props.disabled).toBe(false)
|
|
})
|
|
|
|
it('hides raw provider errors behind the localized OAuth boundary', async () => {
|
|
mockSignInWithOAuthProvider.mockRejectedValue(new Error('raw provider secret'))
|
|
const renderer = await renderScreen()
|
|
|
|
await act(async () => {
|
|
renderer.root.findByProps({ testID: 'oauth-google' }).props.onPress()
|
|
await Promise.resolve()
|
|
await Promise.resolve()
|
|
})
|
|
|
|
expect(mockAlert).toHaveBeenCalledWith(
|
|
'mobile.auth.loginFailedTitle',
|
|
'mobile.auth.oauthFailed',
|
|
)
|
|
})
|
|
|
|
it('does not render the Google CTA when the server disables the provider', async () => {
|
|
mockGetAuthCapabilities.mockReset().mockResolvedValue({
|
|
...ENABLED_CAPABILITIES,
|
|
googleEnabled: false,
|
|
})
|
|
const renderer = await renderScreen()
|
|
|
|
expect(() => renderer.root.findByProps({ testID: 'oauth-google' })).toThrow()
|
|
})
|
|
})
|