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
218
apps/mobile-rn/__tests__/signup-oauth-screen.test.tsx
Normal file
218
apps/mobile-rn/__tests__/signup-oauth-screen.test.tsx
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
import React from 'react'
|
||||
import { Linking } from 'react-native'
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
||||
|
||||
const mockGetAuthCapabilities = jest.fn()
|
||||
const mockSignInWithOAuthProvider = jest.fn()
|
||||
const mockNavigation = {
|
||||
canGoBack: jest.fn(() => true),
|
||||
goBack: jest.fn(),
|
||||
navigate: jest.fn(),
|
||||
}
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
useNavigation: () => mockNavigation,
|
||||
}))
|
||||
jest.mock('react-native-safe-area-context', () => ({
|
||||
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
|
||||
}))
|
||||
jest.mock('@d3ro/i18n', () => ({
|
||||
useI18n: () => ({
|
||||
locale: 'ko',
|
||||
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' },
|
||||
tag: { red: '#f66' },
|
||||
},
|
||||
}),
|
||||
}))
|
||||
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: {
|
||||
signUp: jest.fn(),
|
||||
resend: jest.fn(),
|
||||
},
|
||||
},
|
||||
}))
|
||||
jest.mock('../src/lib/auth-redirect', () => ({
|
||||
AUTH_REDIRECT_URL: 'd3ro-voice://auth-callback',
|
||||
}))
|
||||
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 SignUpScreen from '../src/screens/SignUpScreen'
|
||||
|
||||
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(<SignUpScreen />)
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
})
|
||||
return renderer!
|
||||
}
|
||||
|
||||
describe('SignUpScreen Google OAuth discoverability', () => {
|
||||
beforeEach(() => {
|
||||
mockGetAuthCapabilities.mockReset().mockResolvedValue(ENABLED_CAPABILITIES)
|
||||
mockSignInWithOAuthProvider.mockReset().mockResolvedValue('cancelled')
|
||||
jest.spyOn(Linking, 'openURL').mockResolvedValue(true)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('shows an accessible Google sign-up CTA only when the server enables it', async () => {
|
||||
const enabled = await renderScreen()
|
||||
const button = enabled.root.findByProps({ testID: 'signup-oauth-google' })
|
||||
|
||||
expect(button.props.accessibilityLabel).toBe('mobile.signup.google')
|
||||
expect(button.props.accessibilityHint).toBe('mobile.signup.googleHint')
|
||||
expect(button.props.accessibilityState).toEqual({ disabled: true, busy: false })
|
||||
expect(enabled.root.findByProps({ testID: 'sign-up-terms' })).toBeTruthy()
|
||||
expect(enabled.root.findByProps({ testID: 'sign-up-submit' })).toBeTruthy()
|
||||
|
||||
mockGetAuthCapabilities.mockReset().mockResolvedValue({
|
||||
...ENABLED_CAPABILITIES,
|
||||
signUpEnabled: false,
|
||||
})
|
||||
const existingGoogleAccount = await renderScreen()
|
||||
expect(existingGoogleAccount.root.findByProps({ testID: 'signup-oauth-google' })).toBeTruthy()
|
||||
|
||||
mockGetAuthCapabilities.mockReset().mockResolvedValue({
|
||||
...ENABLED_CAPABILITIES,
|
||||
googleEnabled: false,
|
||||
})
|
||||
const disabled = await renderScreen()
|
||||
expect(() => disabled.root.findByProps({ testID: 'signup-oauth-google' })).toThrow()
|
||||
expect(disabled.root.findByProps({ testID: 'sign-up-submit' })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('locks both sign-up paths while OAuth is active and treats cancellation as non-fatal', 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: 'sign-up-terms' }).props.onPress()
|
||||
})
|
||||
const button = renderer.root.findByProps({ testID: 'signup-oauth-google' })
|
||||
|
||||
await act(async () => {
|
||||
button.props.onPress()
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
expect(mockSignInWithOAuthProvider).toHaveBeenCalledWith('google')
|
||||
expect(renderer.root.findByProps({ testID: 'signup-oauth-google' }).props.disabled).toBe(true)
|
||||
expect(renderer.root.findByProps({ testID: 'signup-oauth-google' }).props.accessibilityState)
|
||||
.toEqual({ disabled: true, busy: true })
|
||||
expect(renderer.root.findByProps({ testID: 'signup-oauth-google-loading' })).toBeTruthy()
|
||||
expect(renderer.root.findByProps({ testID: 'sign-up-submit' }).props.disabled).toBe(true)
|
||||
|
||||
await act(async () => {
|
||||
resolveOAuth?.('cancelled')
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
expect(renderer.root.findByProps({ testID: 'signup-oauth-google' }).props.disabled).toBe(false)
|
||||
expect(() => renderer.root.findByProps({ testID: 'auth-error' })).toThrow()
|
||||
})
|
||||
|
||||
it('renders a localized fail-closed error without removing email sign-up', async () => {
|
||||
mockSignInWithOAuthProvider.mockRejectedValue(new Error('raw provider secret'))
|
||||
const renderer = await renderScreen()
|
||||
|
||||
await act(async () => {
|
||||
renderer.root.findByProps({ testID: 'sign-up-terms' }).props.onPress()
|
||||
})
|
||||
await act(async () => {
|
||||
renderer.root.findByProps({ testID: 'signup-oauth-google' }).props.onPress()
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
const error = renderer.root.findByProps({ testID: 'auth-error' })
|
||||
expect(error.findAllByProps({ children: 'mobile.auth.oauthFailed' }).length).toBeGreaterThan(0)
|
||||
expect(error.findAllByProps({ children: 'raw provider secret' })).toHaveLength(0)
|
||||
expect(renderer.root.findByProps({ testID: 'sign-up-submit' })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('fails closed when capability discovery fails', async () => {
|
||||
mockGetAuthCapabilities.mockReset().mockRejectedValue(new Error('offline'))
|
||||
const renderer = await renderScreen()
|
||||
|
||||
expect(() => renderer.root.findByProps({ testID: 'signup-oauth-google' })).toThrow()
|
||||
expect(renderer.root.findByProps({ testID: 'sign-up-submit' })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('never starts Google account creation before explicit terms acceptance', async () => {
|
||||
const renderer = await renderScreen()
|
||||
const button = renderer.root.findByProps({ testID: 'signup-oauth-google' })
|
||||
|
||||
expect(button.props.disabled).toBe(true)
|
||||
await act(async () => {
|
||||
button.props.onPress()
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
expect(mockSignInWithOAuthProvider).not.toHaveBeenCalled()
|
||||
expect(renderer.root.findByProps({ testID: 'auth-error' })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('opens both legal documents without accepting the terms on the user\'s behalf', async () => {
|
||||
const renderer = await renderScreen()
|
||||
|
||||
await act(async () => {
|
||||
renderer.root.findByProps({ testID: 'sign-up-terms-link' }).props.onPress()
|
||||
await Promise.resolve()
|
||||
})
|
||||
await act(async () => {
|
||||
renderer.root.findByProps({ testID: 'sign-up-privacy-link' }).props.onPress()
|
||||
await Promise.resolve()
|
||||
})
|
||||
|
||||
expect(Linking.openURL).toHaveBeenNthCalledWith(1, 'https://d3ro.chanpaca.net/terms/')
|
||||
expect(Linking.openURL).toHaveBeenNthCalledWith(2, 'https://d3ro.chanpaca.net/privacy/')
|
||||
expect(renderer.root.findByProps({ testID: 'sign-up-terms' }).props.accessibilityState)
|
||||
.toEqual({ checked: false, disabled: false })
|
||||
expect(renderer.root.findByProps({ testID: 'signup-oauth-google' }).props.disabled).toBe(true)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue