feat: update RefreshControl mocks in tests and improve error handling in useChangePassword hook
This commit is contained in:
parent
8f00d4644a
commit
cd1a4f6841
|
|
@ -28,7 +28,13 @@ jest.mock('@/components/icon', () => ({
|
|||
// Mock components
|
||||
jest.mock('@/components/ErrorState', () => 'ErrorState')
|
||||
jest.mock('@/components/LoadingState', () => 'LoadingState')
|
||||
jest.mock('@/components/RefreshControl', () => 'RefreshControl')
|
||||
|
||||
// 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', () => ({
|
||||
|
|
|
|||
|
|
@ -44,9 +44,15 @@ jest.mock('@/components/icon', () => ({
|
|||
// Mock UI components
|
||||
jest.mock('@/components/LoadingState', () => 'LoadingState')
|
||||
jest.mock('@/components/ErrorState', () => 'ErrorState')
|
||||
jest.mock('@/components/RefreshControl', () => 'RefreshControl')
|
||||
jest.mock('@/components/PaginationLoader', () => 'PaginationLoader')
|
||||
|
||||
// Mock react-native RefreshControl (directly from react-native)
|
||||
jest.mock('react-native', () =>
|
||||
Object.assign({}, jest.requireActual('react-native'), {
|
||||
RefreshControl: 'RefreshControl',
|
||||
})
|
||||
)
|
||||
|
||||
// Mock hooks
|
||||
jest.mock('@/hooks', () => ({
|
||||
useTemplates: jest.fn(() => ({
|
||||
|
|
|
|||
|
|
@ -57,10 +57,12 @@ jest.mock('@/components/PaginationLoader', () => ({
|
|||
default: ({ testID }: any) => <View testID={testID} />,
|
||||
}))
|
||||
|
||||
jest.mock('@/components/RefreshControl', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
// Mock react-native RefreshControl (directly from react-native)
|
||||
jest.mock('react-native', () =>
|
||||
Object.assign({}, jest.requireActual('react-native'), {
|
||||
RefreshControl: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
})
|
||||
)
|
||||
|
||||
jest.mock('@/hooks', () => ({
|
||||
useTemplateGenerations: jest.fn(),
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export default function SearchResultsScreen() {
|
|||
aspectRatio: template.aspectRatio ? parseFloat(template.aspectRatio as string) : undefined,
|
||||
}))
|
||||
|
||||
if (loading && !refreshing) {
|
||||
if (loading) {
|
||||
return (
|
||||
<SafeAreaView style={styles.container} edges={['top']}>
|
||||
<StatusBar style="light" />
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ describe('HeroSlider Component', () => {
|
|||
describe('Callback Behavior', () => {
|
||||
it('should call onActivityPress with link when activity is pressed', () => {
|
||||
const onActivityPress = jest.fn()
|
||||
const props = {
|
||||
const props: { activities: typeof mockActivities; onActivityPress?: (link: string) => void } = {
|
||||
activities: mockActivities,
|
||||
onActivityPress,
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ describe('HeroSlider Component', () => {
|
|||
})
|
||||
|
||||
it('should not throw when onActivityPress is not provided', () => {
|
||||
const props = { activities: mockActivities }
|
||||
const props: { activities: typeof mockActivities; onActivityPress?: (link: string) => void } = { activities: mockActivities }
|
||||
expect(() => {
|
||||
if (props.onActivityPress) {
|
||||
props.onActivityPress(mockActivities[0].link)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
import { View, Text, Pressable, ScrollView, StyleSheet } from 'react-native'
|
||||
import { Image } from 'expo-image'
|
||||
|
||||
interface Activity {
|
||||
export interface Activity {
|
||||
id: string
|
||||
title: string
|
||||
titleEn?: string
|
||||
|
|
@ -20,7 +20,7 @@ interface HeroSliderProps {
|
|||
export function HeroSlider({
|
||||
activities,
|
||||
onActivityPress,
|
||||
}: HeroSliderProps): JSX.Element | null {
|
||||
}: HeroSliderProps): React.ReactNode | null {
|
||||
// 空数据时返回 null
|
||||
if (!activities || activities.length === 0) {
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export function TabNavigation({
|
|||
isSticky = false,
|
||||
wrapperStyle,
|
||||
onLayout,
|
||||
}: TabNavigationProps): JSX.Element {
|
||||
}: TabNavigationProps): React.ReactNode {
|
||||
const scrollViewRef = useRef<ScrollView>(null)
|
||||
const tabLayouts = useRef<{ x: number; width: number }[]>([])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { memo, useCallback, useMemo } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View, ViewStyle } from 'react-native'
|
||||
import { Pressable, StyleSheet, Text, View, ViewStyle, ImageStyle } from 'react-native'
|
||||
import { Image } from 'expo-image'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ const TemplateCardComponent: React.FC<TemplateCardProps> = ({
|
|||
[aspectRatio]
|
||||
)
|
||||
const imageStyle = useMemo(() =>
|
||||
[styles.cardImage, aspectRatio ? { aspectRatio } : undefined].filter(Boolean) as ViewStyle[],
|
||||
[styles.cardImage, aspectRatio ? { aspectRatio } : undefined].filter(Boolean) as ImageStyle[],
|
||||
[aspectRatio]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export function TitleBar({
|
|||
onPointsPress,
|
||||
onSearchPress,
|
||||
onLayout,
|
||||
}: TitleBarProps): JSX.Element {
|
||||
}: TitleBarProps): React.ReactNode {
|
||||
return (
|
||||
<View
|
||||
testID="title-bar"
|
||||
|
|
|
|||
|
|
@ -24,22 +24,22 @@ export const useChangePassword = (): ChangePasswordResult => {
|
|||
|
||||
// 客户端验证
|
||||
if (!oldPassword) {
|
||||
setError({ message: '旧密码不能为空' })
|
||||
setError({ message: '旧密码不能为空', status: 400, statusText: 'Bad Request' })
|
||||
return
|
||||
}
|
||||
|
||||
if (newPassword.length < 6) {
|
||||
setError({ message: '新密码长度至少为6位' })
|
||||
setError({ message: '新密码长度至少为6位', status: 400, statusText: 'Bad Request' })
|
||||
return
|
||||
}
|
||||
|
||||
if (oldPassword === newPassword) {
|
||||
setError({ message: '新密码不能与当前密码相同' })
|
||||
setError({ message: '新密码不能与当前密码相同', status: 400, statusText: 'Bad Request' })
|
||||
return
|
||||
}
|
||||
|
||||
if (confirmPassword !== undefined && newPassword !== confirmPassword) {
|
||||
setError({ message: '新密码和确认密码不一致' })
|
||||
setError({ message: '新密码和确认密码不一致', status: 400, statusText: 'Bad Request' })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ export const useChangePassword = (): ChangePasswordResult => {
|
|||
|
||||
const result = await handleError(async () => {
|
||||
return await authClient.changePassword({
|
||||
oldPassword,
|
||||
currentPassword: oldPassword,
|
||||
newPassword,
|
||||
revokeOtherSessions: true,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { OWNER_ID } from '@/lib/auth'
|
|||
import { handleError } from './use-error'
|
||||
|
||||
|
||||
type ListTemplatesParams = Omit<ListTemplatesInput, 'ownerId'>
|
||||
type ListTemplatesParams = Partial<Omit<ListTemplatesInput, 'ownerId'>>
|
||||
|
||||
const DEFAULT_PARAMS = {
|
||||
limit: 20,
|
||||
|
|
|
|||
|
|
@ -17,9 +17,11 @@ import { type ApiError } from '@/lib/types'
|
|||
import { handleError } from './use-error'
|
||||
|
||||
// Type definitions
|
||||
export type WorksCategory = '全部' | '萌宠' | '写真' | '合拍'
|
||||
|
||||
export interface UseWorksSearchParams {
|
||||
keyword: string
|
||||
category?: '全部' | '萌宠' | '写真' | '合拍'
|
||||
category?: WorksCategory
|
||||
page?: number
|
||||
limit?: number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import { renderHook, waitFor, act } from '@testing-library/react-native'
|
||||
import { useWorksSearch } from '@/hooks/use-works-search'
|
||||
import { useWorksSearch, type WorksCategory } from '@/hooks/use-works-search'
|
||||
import { root } from '@repo/core'
|
||||
import { TemplateGenerationController } from '@repo/sdk'
|
||||
|
||||
|
|
@ -21,15 +21,15 @@ jest.mock('@repo/core', () => ({
|
|||
|
||||
// Mock @tanstack/react-query before importing the hook
|
||||
const mockRefetch = jest.fn()
|
||||
const mockUseQuery = jest.fn(() => ({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: mockRefetch,
|
||||
}))
|
||||
const mockUseQuery = jest.fn()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const mockUseQueryImpl = (...args: any[]) => {
|
||||
return mockUseQuery(args[0], args[1])
|
||||
}
|
||||
|
||||
jest.mock('@tanstack/react-query', () => ({
|
||||
useQuery: jest.fn((args) => mockUseQuery(args)),
|
||||
useQuery: mockUseQueryImpl,
|
||||
}))
|
||||
|
||||
describe('useWorksSearch', () => {
|
||||
|
|
@ -267,16 +267,16 @@ describe('useWorksSearch', () => {
|
|||
})
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ keyword, category }) => useWorksSearch({ keyword, category }),
|
||||
({ keyword, category }: { keyword: string; category?: WorksCategory }) => useWorksSearch({ keyword, category }),
|
||||
{
|
||||
initialProps: { keyword: '测试', category: '萌宠' as const },
|
||||
initialProps: { keyword: '测试', category: '萌宠' },
|
||||
}
|
||||
)
|
||||
|
||||
expect(result.current.works).toEqual(mockData1.data)
|
||||
|
||||
// Switch category
|
||||
rerender({ keyword: '测试', category: '写真' as const })
|
||||
rerender({ keyword: '测试', category: '写真' })
|
||||
|
||||
expect(result.current.works).toEqual(mockData2.data)
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue