355 lines
11 KiB
TypeScript
355 lines
11 KiB
TypeScript
import React from 'react'
|
|
import { render, waitFor } from '@testing-library/react-native'
|
|
import HomeScreen from '../index'
|
|
|
|
// 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 icons
|
|
jest.mock('@/components/icon', () => ({
|
|
DownArrowIcon: () => 'DownArrowIcon',
|
|
PointsIcon: () => 'PointsIcon',
|
|
SearchIcon: () => 'SearchIcon',
|
|
}))
|
|
|
|
// Mock components
|
|
jest.mock('@/components/ErrorState', () => 'ErrorState')
|
|
jest.mock('@/components/LoadingState', () => 'LoadingState')
|
|
|
|
// Mock home blocks
|
|
jest.mock('@/components/blocks/home', () => ({
|
|
TitleBar: 'TitleBar',
|
|
HeroSlider: 'HeroSlider',
|
|
TabNavigation: 'TabNavigation',
|
|
TemplateCard: 'TemplateCard',
|
|
}))
|
|
|
|
// Mock react-native RefreshControl (directly from react-native)
|
|
jest.mock('react-native', () =>
|
|
Object.assign({}, jest.requireActual('react-native'), {
|
|
RefreshControl: 'RefreshControl',
|
|
})
|
|
)
|
|
|
|
// Mock dependencies
|
|
jest.mock('expo-router', () => ({
|
|
useLocalSearchParams: jest.fn(() => ({})),
|
|
useRouter: jest.fn(() => ({
|
|
push: jest.fn(),
|
|
})),
|
|
}))
|
|
|
|
jest.mock('react-i18next', () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string) => key,
|
|
}),
|
|
}))
|
|
|
|
jest.mock('@/hooks/use-activates', () => ({
|
|
useActivates: jest.fn(() => ({
|
|
load: jest.fn(),
|
|
data: {
|
|
activities: [
|
|
{
|
|
id: '1',
|
|
title: 'Test Activity',
|
|
desc: 'Test Description',
|
|
coverUrl: 'https://example.com/image.jpg',
|
|
link: '/test',
|
|
},
|
|
],
|
|
},
|
|
error: null,
|
|
})),
|
|
}))
|
|
|
|
jest.mock('@/hooks/use-categories', () => ({
|
|
useCategories: jest.fn(() => ({
|
|
load: jest.fn(),
|
|
data: {
|
|
categories: [
|
|
{
|
|
id: 'cat1',
|
|
name: 'Test Category',
|
|
templates: [
|
|
{
|
|
id: 'template1',
|
|
title: 'Test Template',
|
|
webpPreviewUrl: 'https://example.com/preview.webp',
|
|
previewUrl: 'https://example.com/preview.jpg',
|
|
coverImageUrl: 'https://example.com/cover.png',
|
|
aspectRatio: '1:1',
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
loading: false,
|
|
error: null,
|
|
})),
|
|
}))
|
|
|
|
jest.mock('@/hooks/use-user-balance', () => ({
|
|
useUserBalance: jest.fn(() => ({
|
|
balance: 1234,
|
|
loading: false,
|
|
error: null,
|
|
})),
|
|
}))
|
|
|
|
jest.mock('@/hooks/use-categories-with-tags', () => ({
|
|
useCategoriesWithTags: jest.fn(() => ({
|
|
load: jest.fn(),
|
|
data: {
|
|
categories: [
|
|
{
|
|
id: 'cat1',
|
|
name: 'Test Category',
|
|
},
|
|
],
|
|
},
|
|
loading: false,
|
|
error: null,
|
|
})),
|
|
}))
|
|
|
|
jest.mock('@/hooks/use-category-templates', () => ({
|
|
useCategoryTemplates: jest.fn(() => ({
|
|
templates: [
|
|
{
|
|
id: 'template1',
|
|
title: 'Test Template',
|
|
webpPreviewUrl: 'https://example.com/preview.webp',
|
|
previewUrl: 'https://example.com/preview.jpg',
|
|
coverImageUrl: 'https://example.com/cover.png',
|
|
aspectRatio: '1:1',
|
|
},
|
|
],
|
|
loading: false,
|
|
loadingMore: false,
|
|
execute: jest.fn(),
|
|
loadMore: jest.fn(),
|
|
hasMore: false,
|
|
})),
|
|
}))
|
|
|
|
jest.mock('@/hooks/use-sticky-tabs', () => ({
|
|
useStickyTabs: jest.fn(() => ({
|
|
isSticky: false,
|
|
tabsHeight: 0,
|
|
titleBarHeightRef: { current: 0 },
|
|
handleScroll: jest.fn(),
|
|
handleTabsLayout: jest.fn(),
|
|
handleTitleBarLayout: jest.fn(),
|
|
})),
|
|
}))
|
|
|
|
jest.mock('@/hooks/use-tab-navigation', () => ({
|
|
useTabNavigation: jest.fn(() => ({
|
|
activeIndex: 0,
|
|
selectedCategoryId: 'cat1',
|
|
tabs: [{ id: 'cat1', name: 'Test Category' }],
|
|
selectTab: jest.fn(),
|
|
selectCategoryById: jest.fn(),
|
|
})),
|
|
}))
|
|
|
|
jest.mock('@/hooks/use-template-filter', () => ({
|
|
useTemplateFilter: jest.fn(({ templates }) => ({
|
|
filteredTemplates: templates,
|
|
})),
|
|
}))
|
|
|
|
const renderWithProviders = (component: React.ReactElement) => {
|
|
return render(component)
|
|
}
|
|
|
|
describe('HomeScreen', () => {
|
|
it('should render title bar with app name', async () => {
|
|
const { getByText } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('Popcore')).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
it('should render category tabs when data is loaded', async () => {
|
|
const { getByText } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('Test Category')).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
it('should render template cards when category has templates', async () => {
|
|
const { getByText } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('Test Template')).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
it('should not show loading state when data is loaded', async () => {
|
|
const { queryByText } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
expect(queryByText('加载中')).toBeNull()
|
|
})
|
|
})
|
|
|
|
it('should display user balance from useUserBalance hook', async () => {
|
|
const { getByText } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
expect(getByText('1234')).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
describe('Loading States', () => {
|
|
it('should show only one loading indicator when categories are loading', async () => {
|
|
const { useCategoriesWithTags } = require('@/hooks/use-categories-with-tags')
|
|
const { useCategoryTemplates } = require('@/hooks/use-category-templates')
|
|
|
|
useCategoriesWithTags.mockReturnValue({
|
|
load: jest.fn(),
|
|
data: null,
|
|
loading: true,
|
|
error: null,
|
|
})
|
|
|
|
useCategoryTemplates.mockReturnValue({
|
|
templates: [],
|
|
loading: false,
|
|
loadingMore: false,
|
|
execute: jest.fn(),
|
|
loadMore: jest.fn(),
|
|
hasMore: false,
|
|
})
|
|
|
|
const { UNSAFE_getAllByType } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
// 应该只有一个 LoadingState 组件
|
|
const loadingStates = UNSAFE_getAllByType('LoadingState' as any)
|
|
expect(loadingStates.length).toBe(1)
|
|
})
|
|
})
|
|
|
|
it('should show only one loading indicator when templates are loading', async () => {
|
|
const { useCategoriesWithTags } = require('@/hooks/use-categories-with-tags')
|
|
const { useCategoryTemplates } = require('@/hooks/use-category-templates')
|
|
|
|
useCategoriesWithTags.mockReturnValue({
|
|
load: jest.fn(),
|
|
data: {
|
|
categories: [{ id: 'cat1', name: 'Test Category' }],
|
|
},
|
|
loading: false,
|
|
error: null,
|
|
})
|
|
|
|
useCategoryTemplates.mockReturnValue({
|
|
templates: [],
|
|
loading: true,
|
|
loadingMore: false,
|
|
execute: jest.fn(),
|
|
loadMore: jest.fn(),
|
|
hasMore: false,
|
|
})
|
|
|
|
const { UNSAFE_queryAllByType } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
// 应该只有一个 ActivityIndicator
|
|
const activityIndicators = UNSAFE_queryAllByType('ActivityIndicator' as any)
|
|
expect(activityIndicators.length).toBeLessThanOrEqual(1)
|
|
})
|
|
})
|
|
|
|
it('should NOT show multiple loading indicators simultaneously', async () => {
|
|
const { useCategoriesWithTags } = require('@/hooks/use-categories-with-tags')
|
|
const { useCategoryTemplates } = require('@/hooks/use-category-templates')
|
|
|
|
// 模拟两个都在加载的情况
|
|
useCategoriesWithTags.mockReturnValue({
|
|
load: jest.fn(),
|
|
data: null,
|
|
loading: true,
|
|
error: null,
|
|
})
|
|
|
|
useCategoryTemplates.mockReturnValue({
|
|
templates: [],
|
|
loading: true,
|
|
loadingMore: false,
|
|
execute: jest.fn(),
|
|
loadMore: jest.fn(),
|
|
hasMore: false,
|
|
})
|
|
|
|
const { UNSAFE_queryAllByType } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
// 即使两个都在加载,也应该只显示一个 loading
|
|
const loadingStates = UNSAFE_queryAllByType('LoadingState' as any)
|
|
const activityIndicators = UNSAFE_queryAllByType('ActivityIndicator' as any)
|
|
|
|
// 总共的 loading 指示器不应该超过 1 个
|
|
const totalLoadingIndicators = loadingStates.length + activityIndicators.length
|
|
expect(totalLoadingIndicators).toBeLessThanOrEqual(1)
|
|
})
|
|
})
|
|
|
|
it('should show unified loading state during initial load', async () => {
|
|
const { useCategoriesWithTags } = require('@/hooks/use-categories-with-tags')
|
|
const { useCategoryTemplates } = require('@/hooks/use-category-templates')
|
|
|
|
useCategoriesWithTags.mockReturnValue({
|
|
load: jest.fn(),
|
|
data: null,
|
|
loading: true,
|
|
error: null,
|
|
})
|
|
|
|
useCategoryTemplates.mockReturnValue({
|
|
templates: [],
|
|
loading: true,
|
|
loadingMore: false,
|
|
execute: jest.fn(),
|
|
loadMore: jest.fn(),
|
|
hasMore: false,
|
|
})
|
|
|
|
const { UNSAFE_queryAllByType } = renderWithProviders(<HomeScreen />)
|
|
|
|
await waitFor(() => {
|
|
// 应该只显示一个统一的 loading 状态
|
|
const allLoadingComponents = [
|
|
...UNSAFE_queryAllByType('LoadingState' as any),
|
|
...UNSAFE_queryAllByType('ActivityIndicator' as any),
|
|
]
|
|
expect(allLoadingComponents.length).toBe(1)
|
|
})
|
|
})
|
|
})
|
|
})
|