feat: add mobile shell and unified search
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Failing after 50s
ci / 워크스페이스 빌드 검증 (push) Has been skipped
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 20s
ci / .NET API 서버 테스트 (push) Successful in 12s
deploy-site / deploy (push) Successful in 39s
ci / 모바일 린트·타입·Jest (push) Failing after 3h10m31s

This commit is contained in:
Yun Chan 2026-09-28 23:23:36 +09:00
parent f505a03d34
commit 5ca3709b32
98 changed files with 5168 additions and 2093 deletions

View file

@ -7,6 +7,7 @@ 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')
let mockProfileTier: 'free' | 'pro' | 'pro_plus' = 'free'
const mockI18n = {
t: (key: string, params?: Record<string, unknown>) => params === undefined ? key : `${key}:${JSON.stringify(params)}`,
formatDate: () => '2026-08-21',
@ -20,7 +21,9 @@ const mockUser = {
identities: [],
}
jest.mock('@react-navigation/native', () => ({ useNavigation: () => ({ navigate: jest.fn() }) }))
jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({ navigate: jest.fn(), goBack: jest.fn() }),
}))
jest.mock('react-native-safe-area-context', () => ({
useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }),
}))
@ -40,8 +43,24 @@ jest.mock('../src/lib/preferences-context', () => ({
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' },
accent: {
main: '#7cf',
light: '#7cf',
dim: '#234',
solid: '#37c',
solidHover: '#258',
ink: '#fff',
},
tag: { red: '#f66', green: '#6f6', orange: '#fa6' },
status: {
success: '#6f6',
warning: '#fa6',
danger: '#f66',
info: '#7cf',
dangerBg: '#411',
},
overlay: { soft: '#222' },
led: { off: '#555' },
},
}),
clearAllUserPreferenceCaches: jest.fn(),
@ -53,7 +72,7 @@ jest.mock('../src/lib/supabase', () => ({
select: () => ({
eq: () => ({
single: async () => ({
data: { id: USER_ID, name: 'User', avatar_url: null, locale: 'en', tier: 'free', created_at: '', updated_at: '' },
data: { id: USER_ID, name: 'User', avatar_url: null, locale: 'en', tier: mockProfileTier, created_at: '', updated_at: '' },
error: null,
}),
}),
@ -118,6 +137,7 @@ async function flush(): Promise<void> {
describe('AccountScreen identity management', () => {
beforeEach(() => {
mockProfileTier = 'free'
mockCapabilities.mockResolvedValue({
signUpEnabled: true,
emailEnabled: true,
@ -149,4 +169,17 @@ describe('AccountScreen identity management', () => {
})
expect(mockOpenUrl).toHaveBeenCalledWith('https://provider.example/authorize')
})
it('shows the localized PRO+ label for a pro_plus profile', async () => {
mockProfileTier = 'pro_plus'
let renderer: ReactTestRenderer.ReactTestRenderer
await act(async () => {
renderer = create(<AccountScreen identityService={createIdentityService()} />)
})
await flush()
expect(renderer!.root.findAllByProps({ children: 'mobile.paywall.proPlus' }).length).toBeGreaterThan(0)
expect(renderer!.root.findAllByProps({ children: 'mobile.account.tierUnknown' })).toHaveLength(0)
})
})