import React from 'react' import { act, create } from 'react-test-renderer' import type { ReactTestRenderer } from 'react-test-renderer' import { Linking } from 'react-native' import { IdentityManagementService } from '../src/features/auth/identity-linking' const USER_ID = '11111111-1111-4111-8111-111111111111' const mockCapabilities = jest.fn() const mockOpenUrl = jest.spyOn(Linking, 'openURL') const mockI18n = { t: (key: string, params?: Record) => params === undefined ? key : `${key}:${JSON.stringify(params)}`, formatDate: () => '2026-08-21', } const mockUser = { id: USER_ID, email: 'user@example.com', created_at: '2026-08-01T00:00:00.000Z', last_sign_in_at: '2026-08-21T00:00:00.000Z', app_metadata: { provider: 'google', providers: ['email', 'google'] }, identities: [], } jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({ navigate: jest.fn() }) })) jest.mock('react-native-safe-area-context', () => ({ useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), })) jest.mock('@d3ro/i18n', () => ({ useI18n: () => mockI18n, })) jest.mock('../src/lib/auth-context', () => ({ useAuth: () => ({ user: mockUser, purgeLocalSession: jest.fn(async () => undefined), }), })) jest.mock('../src/lib/device-context', () => ({ useDevice: () => ({ currentDevice: null }) })) jest.mock('../src/lib/preferences-context', () => ({ useMobilePreferences: () => ({ palette: { bg: { app: '#000', card: '#181818', inset: '#202020', cardHover: '#222' }, text: { primary: '#fff', muted: '#999', white: '#fff', onAccent: '#000' }, border: { default: '#444', subtle: '#333', strong: '#666' }, accent: { main: '#7cf', dim: '#234' }, tag: { red: '#f66', green: '#6f6', orange: '#fa6' }, }, }), clearAllUserPreferenceCaches: jest.fn(), })) jest.mock('../src/lib/auth-capabilities', () => ({ getAuthCapabilities: (...args: unknown[]) => mockCapabilities(...args) })) jest.mock('../src/lib/supabase', () => ({ supabase: { from: () => ({ select: () => ({ eq: () => ({ single: async () => ({ data: { id: USER_ID, name: 'User', avatar_url: null, locale: 'en', tier: 'free', created_at: '', updated_at: '' }, error: null, }), }), }), }), auth: { signOut: jest.fn() }, functions: { invoke: jest.fn() }, }, })) jest.mock('../src/lib/auth-redirect', () => ({ AUTH_REDIRECT_URL: 'd3ro-voice://auth-callback', completeAuthRedirect: jest.fn(), })) jest.mock('react-native-inappbrowser-reborn', () => ({ __esModule: true, default: { isAvailable: jest.fn(async () => false), openAuth: jest.fn(), }, })) jest.mock('../src/lib/entitlement-context', () => ({ clearAllEntitlementCaches: jest.fn() })) jest.mock('../src/features/history/history-cache', () => ({ clearAllHistoryCaches: jest.fn() })) jest.mock('../src/features/devices/device-service', () => ({ unregisterCurrentDevice: jest.fn() })) jest.mock('../src/features/actions/action-service', () => ({ clearActionHistory: jest.fn() })) jest.mock('../src/features/notifications/notification-service', () => ({ detachPushRegistrationForLogout: jest.fn() })) jest.mock('../src/features/templates', () => ({ clearAllGenerationIdempotencyKeys: jest.fn() })) jest.mock('../src/lib/native-config', () => ({ getMobileRuntimeConfig: () => ({ installationId: 'device' }) })) jest.mock('../src/lib/audio-recorder', () => ({ audioRecorder: { cancel: jest.fn(async () => undefined) } })) jest.mock('../src/features/recording/durable-processing-queue', () => ({ clearQueuedAudioForUser: jest.fn(async () => undefined), })) jest.mock('../src/lib/logout', () => ({ signOutWithLocalFallback: jest.fn(async () => ({ remoteRevocationConfirmed: true })) })) import AccountScreen from '../src/screens/AccountScreen' function createIdentityService(): IdentityManagementService { const identities = [ { id: USER_ID, user_id: USER_ID, identity_id: 'email-id', provider: 'email' }, { id: USER_ID, user_id: USER_ID, identity_id: 'google-id', provider: 'google' }, ] return new IdentityManagementService({ getUser: jest.fn(async () => ({ data: { user: { id: USER_ID, app_metadata: { provider: 'google' } } }, error: null, })), getUserIdentities: jest.fn(async () => ({ data: { identities }, error: null })), linkIdentity: jest.fn(async () => ({ data: { provider: 'github', url: 'https://provider.example/authorize' }, error: null, })), unlinkIdentity: jest.fn(async () => ({ data: {}, error: null })), } as never) } async function flush(): Promise { await act(async () => { await Promise.resolve() await Promise.resolve() await Promise.resolve() }) } describe('AccountScreen identity management', () => { beforeEach(() => { mockCapabilities.mockResolvedValue({ signUpEnabled: true, emailEnabled: true, googleEnabled: true, githubEnabled: true, appleEnabled: false, }) mockOpenUrl.mockReset().mockResolvedValue(true) }) it('shows only capability-enabled unlinked providers and disables the active login unlink', async () => { let renderer: ReactTestRenderer.ReactTestRenderer await act(async () => { renderer = create() }) await flush() expect(renderer!.root.findByProps({ testID: 'account-identities' })).toBeTruthy() expect(renderer!.root.findByProps({ testID: 'link-identity-github' })).toBeTruthy() expect(() => renderer!.root.findByProps({ testID: 'link-identity-google' })).toThrow() expect(() => renderer!.root.findByProps({ testID: 'link-identity-apple' })).toThrow() expect(renderer!.root.findByProps({ testID: 'unlink-identity-google' }).props.disabled).toBe(true) const githubLink = renderer!.root.findByProps({ testID: 'link-identity-github' }) await act(async () => { githubLink.props.onPress() await Promise.resolve() await Promise.resolve() }) expect(mockOpenUrl).toHaveBeenCalledWith('https://provider.example/authorize') }) })