feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
152
apps/mobile-rn/__tests__/account-identities-screen.test.tsx
Normal file
152
apps/mobile-rn/__tests__/account-identities-screen.test.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
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<string, unknown>) => 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<void> {
|
||||
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(<AccountScreen identityService={createIdentityService()} />)
|
||||
})
|
||||
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')
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue