fix: bug
This commit is contained in:
@@ -23,6 +23,10 @@ jest.mock('react-i18next', () => ({
|
||||
|
||||
// Mock expo-router
|
||||
const mockPush = jest.fn()
|
||||
const mockUseFocusEffect = jest.fn((callback: () => void) => {
|
||||
// 立即执行 callback 来模拟页面获得焦点
|
||||
callback()
|
||||
})
|
||||
jest.mock('expo-router', () => ({
|
||||
useRouter: () => ({
|
||||
push: mockPush,
|
||||
@@ -30,6 +34,7 @@ jest.mock('expo-router', () => ({
|
||||
back: jest.fn(),
|
||||
}),
|
||||
useLocalSearchParams: () => ({}),
|
||||
useFocusEffect: (callback: () => void) => mockUseFocusEffect(callback),
|
||||
}))
|
||||
|
||||
// Mock hooks
|
||||
@@ -87,6 +92,7 @@ jest.mock('@/hooks/use-messages', () => ({
|
||||
messages: mockMessages,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
refreshing: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
@@ -524,9 +530,10 @@ describe('MessageScreen', () => {
|
||||
})
|
||||
|
||||
describe('Initial Data Fetch', () => {
|
||||
it('should call execute on mount', () => {
|
||||
it('should call refetch on mount via useFocusEffect', () => {
|
||||
render(<MessageScreen />)
|
||||
expect(mockExecute).toHaveBeenCalled()
|
||||
// 现在使用 useFocusEffect + refetch 替代 useEffect + execute
|
||||
expect(mockRefetch).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call announcement execute on mount', () => {
|
||||
@@ -534,4 +541,144 @@ describe('MessageScreen', () => {
|
||||
expect(mockAnnouncementExecute).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Pull-to-Refresh', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
const { useMessages } = require('@/hooks/use-messages')
|
||||
useMessages.mockReturnValue({
|
||||
messages: mockMessages,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
refreshing: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: true,
|
||||
})
|
||||
|
||||
const { useAnnouncements } = require('@/hooks/use-announcements')
|
||||
useAnnouncements.mockReturnValue({
|
||||
announcements: mockAnnouncements,
|
||||
loading: false,
|
||||
error: null,
|
||||
execute: mockAnnouncementExecute,
|
||||
refetch: jest.fn(),
|
||||
loadMore: jest.fn(),
|
||||
hasMore: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should render FlatList with RefreshControl', () => {
|
||||
const { getByTestId } = render(<MessageScreen />)
|
||||
const flatList = getByTestId('message-list')
|
||||
expect(flatList.props.refreshControl).toBeTruthy()
|
||||
})
|
||||
|
||||
it('should call refetch when pull-to-refresh is triggered', async () => {
|
||||
const { getByTestId } = render(<MessageScreen />)
|
||||
const flatList = getByTestId('message-list')
|
||||
|
||||
// 触发 onRefresh
|
||||
const refreshControl = flatList.props.refreshControl
|
||||
refreshControl.props.onRefresh()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRefetch).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call executeAnnouncements when pull-to-refresh is triggered', async () => {
|
||||
const { getByTestId } = render(<MessageScreen />)
|
||||
const flatList = getByTestId('message-list')
|
||||
|
||||
// 触发 onRefresh
|
||||
const refreshControl = flatList.props.refreshControl
|
||||
refreshControl.props.onRefresh()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAnnouncementExecute).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show refreshing indicator when refreshing is true', () => {
|
||||
const { useMessages } = require('@/hooks/use-messages')
|
||||
useMessages.mockReturnValue({
|
||||
messages: mockMessages,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
refreshing: true,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: true,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<MessageScreen />)
|
||||
const flatList = getByTestId('message-list')
|
||||
const refreshControl = flatList.props.refreshControl
|
||||
|
||||
expect(refreshControl.props.refreshing).toBe(true)
|
||||
})
|
||||
|
||||
it('should not show refreshing indicator when refreshing is false', () => {
|
||||
const { getByTestId } = render(<MessageScreen />)
|
||||
const flatList = getByTestId('message-list')
|
||||
const refreshControl = flatList.props.refreshControl
|
||||
|
||||
expect(refreshControl.props.refreshing).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Tab Focus Reload', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
const { useMessages } = require('@/hooks/use-messages')
|
||||
useMessages.mockReturnValue({
|
||||
messages: mockMessages,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
refreshing: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: true,
|
||||
})
|
||||
|
||||
const { useAnnouncements } = require('@/hooks/use-announcements')
|
||||
useAnnouncements.mockReturnValue({
|
||||
announcements: mockAnnouncements,
|
||||
loading: false,
|
||||
error: null,
|
||||
execute: mockAnnouncementExecute,
|
||||
refetch: jest.fn(),
|
||||
loadMore: jest.fn(),
|
||||
hasMore: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('should call refetch when tab gains focus', () => {
|
||||
render(<MessageScreen />)
|
||||
|
||||
// useFocusEffect 在 mock 中会立即执行 callback
|
||||
// 所以 refetch 应该被调用
|
||||
expect(mockRefetch).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call executeAnnouncements when tab gains focus', () => {
|
||||
render(<MessageScreen />)
|
||||
|
||||
expect(mockAnnouncementExecute).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should use useFocusEffect hook', () => {
|
||||
render(<MessageScreen />)
|
||||
|
||||
// 验证 useFocusEffect 被调用
|
||||
expect(mockUseFocusEffect).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import React, { useState, useCallback } from 'react'
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
@@ -6,11 +6,12 @@ import {
|
||||
FlatList,
|
||||
StatusBar as RNStatusBar,
|
||||
Pressable,
|
||||
RefreshControl,
|
||||
} from 'react-native'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
import { SafeAreaView } from 'react-native-safe-area-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRouter } from 'expo-router'
|
||||
import { useRouter, useFocusEffect } from 'expo-router'
|
||||
|
||||
import { useMessages, type Message } from '@/hooks/use-messages'
|
||||
import { useMessageActions } from '@/hooks/use-message-actions'
|
||||
@@ -54,11 +55,11 @@ export default function MessageScreen() {
|
||||
messages: allMessages,
|
||||
loading,
|
||||
loadingMore,
|
||||
refreshing,
|
||||
error,
|
||||
refetch,
|
||||
loadMore,
|
||||
hasMore,
|
||||
execute,
|
||||
} = useMessages({ limit: 20 })
|
||||
|
||||
const {
|
||||
@@ -75,11 +76,19 @@ export default function MessageScreen() {
|
||||
// Filter messages based on active tab
|
||||
const filteredMessages = filterMessagesByTab(allMessages, activeTab)
|
||||
|
||||
// Initial data fetch
|
||||
useEffect(() => {
|
||||
execute()
|
||||
// Reload data when tab gains focus
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
refetch()
|
||||
executeAnnouncements()
|
||||
}, [refetch, executeAnnouncements])
|
||||
)
|
||||
|
||||
// Handle pull-to-refresh
|
||||
const handleRefresh = useCallback(() => {
|
||||
refetch()
|
||||
executeAnnouncements()
|
||||
}, [])
|
||||
}, [refetch, executeAnnouncements])
|
||||
|
||||
// Handle tab change
|
||||
const handleTabChange = useCallback((type: MessageType) => {
|
||||
@@ -242,6 +251,14 @@ export default function MessageScreen() {
|
||||
onEndReachedThreshold={0.5}
|
||||
ListFooterComponent={renderFooter}
|
||||
ListEmptyComponent={renderEmptyComponent}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={handleRefresh}
|
||||
tintColor="#FFFFFF"
|
||||
colors={['#FFFFFF']}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
)
|
||||
|
||||
@@ -141,19 +141,6 @@ export const MessageCard: React.FC<MessageCardProps> = ({
|
||||
)}
|
||||
|
||||
<View style={styles.contentWrapper}>
|
||||
{/* Left: Icon or Avatar */}
|
||||
<View style={styles.leftSection}>
|
||||
{showAvatar ? (
|
||||
<Image
|
||||
testID="user-avatar"
|
||||
source={{ uri: message.data?.likerAvatar }}
|
||||
style={styles.avatar}
|
||||
/>
|
||||
) : (
|
||||
<Text style={styles.icon}>{icon}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* Middle: Content */}
|
||||
<View style={styles.middleSection}>
|
||||
<Text style={styles.title}>{message.title}</Text>
|
||||
|
||||
@@ -66,6 +66,9 @@ const styles = StyleSheet.create({
|
||||
width: 80,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
borderTopRightRadius: 16,
|
||||
borderBottomRightRadius: 16,
|
||||
},
|
||||
deleteText: {
|
||||
color: '#FFFFFF',
|
||||
|
||||
@@ -22,6 +22,7 @@ const DEFAULT_PARAMS = {
|
||||
export const useMessages = (initialParams?: ListMessagesParams) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [loadingMore, setLoadingMore] = useState(false)
|
||||
const [refreshing, setRefreshing] = useState(false)
|
||||
const [error, setError] = useState<ApiError | null>(null)
|
||||
const [data, setData] = useState<ListMessagesResult>()
|
||||
const currentPageRef = useRef(1)
|
||||
@@ -93,9 +94,12 @@ export const useMessages = (initialParams?: ListMessagesParams) => {
|
||||
return { data: newData, error: null }
|
||||
}, [loading, loadingMore, initialParams])
|
||||
|
||||
const refetch = useCallback(() => {
|
||||
const refetch = useCallback(async () => {
|
||||
hasMoreRef.current = true
|
||||
return execute()
|
||||
setRefreshing(true)
|
||||
const result = await execute()
|
||||
setRefreshing(false)
|
||||
return result
|
||||
}, [execute])
|
||||
|
||||
return {
|
||||
@@ -103,6 +107,7 @@ export const useMessages = (initialParams?: ListMessagesParams) => {
|
||||
messages: data?.messages || [],
|
||||
loading,
|
||||
loadingMore,
|
||||
refreshing,
|
||||
error,
|
||||
execute,
|
||||
refetch,
|
||||
|
||||
Reference in New Issue
Block a user