import React from 'react' import { render, waitFor, fireEvent } from '@testing-library/react-native' import MembershipScreen from '../membership' // Mock react-native-safe-area-context FIRST jest.mock('react-native-safe-area-context', () => ({ SafeAreaProvider: ({ children }: any) => children, SafeAreaView: ({ children }: any) => children, useSafeAreaInsets: () => ({ top: 0, right: 0, bottom: 0, left: 0 }), })) // Mock expo-linear-gradient jest.mock('expo-linear-gradient', () => ({ LinearGradient: 'LinearGradient', })) // Mock expo-image jest.mock('expo-image', () => ({ Image: 'Image', })) // Mock expo-status-bar jest.mock('expo-status-bar', () => ({ StatusBar: 'StatusBar', })) // Mock react-native-svg jest.mock('react-native-svg', () => ({ Svg: 'Svg', Path: 'Path', Defs: 'Defs', LinearGradient: 'LinearGradient', Stop: 'Stop', })) // Mock react-native-reanimated-carousel jest.mock('react-native-reanimated-carousel', () => { const React = require('react') return { __esModule: true, default: ({ data, renderItem, onSnapToItem }: any) => { return React.createElement( 'View', { testID: 'carousel' }, data.map((item: any, index: number) => React.createElement( 'View', { key: index, testID: `carousel-item-${index}` }, renderItem({ item, index }) ) ) ) }, } }) // Mock icons jest.mock('@/components/icon', () => ({ CheckIcon: () => 'CheckIcon', LeftArrowIcon: () => 'LeftArrowIcon', OmitIcon: () => 'OmitIcon', UncheckedIcon: () => 'UncheckedIcon', TermsIcon: () => 'TermsIcon', PrivacyIcon: () => 'PrivacyIcon', })) jest.mock('@/components/icon/checkMark', () => ({ CheckMarkIcon: () => 'CheckMarkIcon', })) // Mock components jest.mock('@/components/drawer/PointsDrawer', () => { const React = require('react') return { __esModule: true, default: () => null, } }) jest.mock('@/components/ui/dropdown', () => { const React = require('react') return { __esModule: true, default: ({ renderTrigger }: any) => { return renderTrigger ? renderTrigger(null, false, jest.fn()) : null }, } }) jest.mock('@/components/GradientText', () => { const React = require('react') return { __esModule: true, default: ({ children }: any) => children, } }) // Mock expo-router jest.mock('expo-router', () => ({ useRouter: jest.fn(() => ({ push: jest.fn(), back: jest.fn(), })), })) // Mock react-i18next jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key, }), })) // Mock hooks - 默认返回空广告数据 const mockLoadActivates = jest.fn() const mockActivatesData = { activities: [], } jest.mock('@/hooks/use-activates', () => ({ useActivates: jest.fn(() => ({ load: mockLoadActivates, data: mockActivatesData, })), })) jest.mock('@/hooks/use-membership', () => ({ useMembership: jest.fn(() => ({ creditPlans: [ { name: 'Plus', amountInCents: 999, popular: false, credits: 100, currency: 'USD', featureList: ['feature1', 'feature2'], }, ], creditBalance: 50, selectedPlanIndex: 0, setSelectedPlanIndex: jest.fn(), isLoadingSubscriptions: false, isStripePricingLoading: false, createSubscription: jest.fn(), upgradeSubscription: jest.fn(), restoreSubscription: jest.fn(), rechargeToken: jest.fn(), activeAuthSubscription: null, hasActiveSubscription: false, stripePricingData: null, })), })) describe('MembershipScreen - Advertisement Carousel', () => { beforeEach(() => { jest.clearAllMocks() }) describe('广告轮播显示', () => { it('当广告数据为空时,应该隐藏轮播图', () => { const { queryByTestId } = render() // 轮播图不应该显示 expect(queryByTestId('carousel')).toBeNull() }) it('当广告数据存在时,应该显示轮播图', () => { // 设置有广告数据的mock const { useActivates } = require('@/hooks/use-activates') useActivates.mockReturnValue({ load: mockLoadActivates, data: { activities: [ { id: '1', title: 'Test Ad 1', desc: 'Description 1', coverUrl: 'https://example.com/ad1.jpg', link: '/ad1', }, { id: '2', title: 'Test Ad 2', desc: 'Description 2', coverUrl: 'https://example.com/ad2.jpg', link: '/ad2', }, ], }, }) const { getByTestId } = render() // 轮播图应该显示 expect(getByTestId('carousel')).toBeTruthy() }) it('应该在组件挂载时加载广告数据', () => { render() // 应该调用 loadActivates expect(mockLoadActivates).toHaveBeenCalled() }) }) describe('广告点击跳转', () => { it('点击广告时应该跳转到正确的链接', () => { const mockPush = jest.fn() const { useRouter } = require('expo-router') useRouter.mockReturnValue({ push: mockPush, back: jest.fn(), }) const { useActivates } = require('@/hooks/use-activates') useActivates.mockReturnValue({ load: mockLoadActivates, data: { activities: [ { id: '1', title: 'Test Ad', desc: 'Description', coverUrl: 'https://example.com/ad.jpg', link: '/test-link', }, ], }, }) const { getByTestId } = render() // 点击第一个广告 const carouselItem = getByTestId('carousel-item-0') fireEvent.press(carouselItem) // 应该跳转到广告链接 expect(mockPush).toHaveBeenCalledWith('/test-link') }) }) describe('广告轮播指示点', () => { it('当有多个广告时,应该显示指示点', () => { const { useActivates } = require('@/hooks/use-activates') useActivates.mockReturnValue({ load: mockLoadActivates, data: { activities: [ { id: '1', title: 'Ad 1', desc: 'Desc 1', coverUrl: 'https://example.com/ad1.jpg', link: '/ad1', }, { id: '2', title: 'Ad 2', desc: 'Desc 2', coverUrl: 'https://example.com/ad2.jpg', link: '/ad2', }, ], }, }) const { getAllByTestId } = render() // 应该有2个指示点 const dots = getAllByTestId(/^dot-/) expect(dots).toHaveLength(2) }) }) })