diff --git a/app/(tabs)/__tests__/index.test.tsx b/app/(tabs)/__tests__/index.test.tsx
index 8e752e1..ec6f7ac 100644
--- a/app/(tabs)/__tests__/index.test.tsx
+++ b/app/(tabs)/__tests__/index.test.tsx
@@ -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', () => ({
diff --git a/app/(tabs)/video.test.tsx b/app/(tabs)/video.test.tsx
index d9d36c8..0bb72a4 100644
--- a/app/(tabs)/video.test.tsx
+++ b/app/(tabs)/video.test.tsx
@@ -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(() => ({
diff --git a/app/generationRecord.test.tsx b/app/generationRecord.test.tsx
index 528bc46..853be80 100644
--- a/app/generationRecord.test.tsx
+++ b/app/generationRecord.test.tsx
@@ -57,10 +57,12 @@ jest.mock('@/components/PaginationLoader', () => ({
default: ({ testID }: any) => ,
}))
-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(),
diff --git a/app/searchResults.tsx b/app/searchResults.tsx
index 6933da7..75689e0 100644
--- a/app/searchResults.tsx
+++ b/app/searchResults.tsx
@@ -56,7 +56,7 @@ export default function SearchResultsScreen() {
aspectRatio: template.aspectRatio ? parseFloat(template.aspectRatio as string) : undefined,
}))
- if (loading && !refreshing) {
+ if (loading) {
return (
diff --git a/components/blocks/home/HeroSlider.test.tsx b/components/blocks/home/HeroSlider.test.tsx
index 5f8b521..d43f263 100644
--- a/components/blocks/home/HeroSlider.test.tsx
+++ b/components/blocks/home/HeroSlider.test.tsx
@@ -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)
diff --git a/components/blocks/home/HeroSlider.tsx b/components/blocks/home/HeroSlider.tsx
index bb2fb32..c9769a8 100644
--- a/components/blocks/home/HeroSlider.tsx
+++ b/components/blocks/home/HeroSlider.tsx
@@ -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
diff --git a/components/blocks/home/TabNavigation.tsx b/components/blocks/home/TabNavigation.tsx
index cb66698..94b48bd 100644
--- a/components/blocks/home/TabNavigation.tsx
+++ b/components/blocks/home/TabNavigation.tsx
@@ -35,7 +35,7 @@ export function TabNavigation({
isSticky = false,
wrapperStyle,
onLayout,
-}: TabNavigationProps): JSX.Element {
+}: TabNavigationProps): React.ReactNode {
const scrollViewRef = useRef(null)
const tabLayouts = useRef<{ x: number; width: number }[]>([])
diff --git a/components/blocks/home/TemplateCard.tsx b/components/blocks/home/TemplateCard.tsx
index b06cd65..f49b4c0 100644
--- a/components/blocks/home/TemplateCard.tsx
+++ b/components/blocks/home/TemplateCard.tsx
@@ -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 = ({
[aspectRatio]
)
const imageStyle = useMemo(() =>
- [styles.cardImage, aspectRatio ? { aspectRatio } : undefined].filter(Boolean) as ViewStyle[],
+ [styles.cardImage, aspectRatio ? { aspectRatio } : undefined].filter(Boolean) as ImageStyle[],
[aspectRatio]
)
diff --git a/components/blocks/home/TitleBar.tsx b/components/blocks/home/TitleBar.tsx
index 34dfe81..048a85d 100644
--- a/components/blocks/home/TitleBar.tsx
+++ b/components/blocks/home/TitleBar.tsx
@@ -14,7 +14,7 @@ export function TitleBar({
onPointsPress,
onSearchPress,
onLayout,
-}: TitleBarProps): JSX.Element {
+}: TitleBarProps): React.ReactNode {
return (
{
// 客户端验证
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,
})
diff --git a/hooks/use-templates.ts b/hooks/use-templates.ts
index 761aa71..c40e1be 100644
--- a/hooks/use-templates.ts
+++ b/hooks/use-templates.ts
@@ -8,7 +8,7 @@ import { OWNER_ID } from '@/lib/auth'
import { handleError } from './use-error'
-type ListTemplatesParams = Omit
+type ListTemplatesParams = Partial>
const DEFAULT_PARAMS = {
limit: 20,
diff --git a/hooks/use-works-search.ts b/hooks/use-works-search.ts
index f8edecc..68c2ce4 100644
--- a/hooks/use-works-search.ts
+++ b/hooks/use-works-search.ts
@@ -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
}
diff --git a/tests/hooks/use-works-search.test.ts b/tests/hooks/use-works-search.test.ts
index c277a36..6d8ba1e 100644
--- a/tests/hooks/use-works-search.test.ts
+++ b/tests/hooks/use-works-search.test.ts
@@ -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)
})