fix: 修复 Jest 测试配置和 useCategories hook 实现

- 修复 jest.setup.js 中不存在的 NativeAnimatedHelper 模块引用
- 优化 jest.config.js 配置,分离 js 和 ts/tsx 的转换配置
- 添加 @testing-library/react 依赖
- 导出 useCategoriesStore 以供测试使用
- 修复 useCategories hook 的参数合并逻辑,确保 inputParams 正确覆盖默认参数
- 修复错误处理时 data 状态设置为 null
- 更新 CategoriesState 类型定义,允许 data 为 null

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
imeepos 2026-01-19 11:46:51 +08:00
parent 687cfcf725
commit a1544ec85f
5 changed files with 2904 additions and 37 deletions

2865
bun.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -9,13 +9,13 @@ import { OWNER_ID } from '@/lib/auth'
import { handleError } from './use-error' import { handleError } from './use-error'
interface CategoriesState { interface CategoriesState {
data: ListCategoriesResult | undefined data: ListCategoriesResult | null | undefined
loading: boolean loading: boolean
error: ApiError | null error: ApiError | null
hasLoaded: boolean hasLoaded: boolean
} }
const useCategoriesStore = create<CategoriesState>((set) => ({ export const useCategoriesStore = create<CategoriesState>((set) => ({
data: undefined, data: undefined,
loading: false, loading: false,
error: null, error: null,
@ -37,22 +37,26 @@ export const useCategories = (params?: ListCategoriesInput) => {
try { try {
setLocalLoading(true) setLocalLoading(true)
useCategoriesStore.setState({ loading: true })
const category = root.get(CategoryController) const category = root.get(CategoryController)
const { data, error } = await handleError(
async () => // Merge params: inputParams > params > defaults
await category.list({ const mergedParams = {
page: 1, page: 1,
limit: 1000, limit: 1000,
isActive: true, isActive: true,
orderBy: 'sortOrder', orderBy: 'sortOrder' as const,
order: 'desc', order: 'desc' as const,
withChildren: true, withChildren: true,
ownerId: OWNER_ID, ownerId: OWNER_ID,
...(inputParams ?? params), ...(params ?? {}),
}), ...(inputParams ?? {}),
) }
const { data, error } = await handleError(async () => await category.list(mergedParams))
if (error) { if (error) {
useCategoriesStore.setState({ error, loading: false, hasLoaded: true }) useCategoriesStore.setState({ data: null, error, loading: false, hasLoaded: true })
return return
} }
useCategoriesStore.setState({ data, loading: false, error: null, hasLoaded: true }) useCategoriesStore.setState({ data, loading: false, error: null, hasLoaded: true })

View File

@ -1,5 +1,3 @@
const { defaults: tsjPresets } = require('ts-jest/presets')
/** @type {import('ts-jest').JestConfigWithTsJest} */ /** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = { module.exports = {
preset: 'react-native', preset: 'react-native',
@ -7,7 +5,14 @@ module.exports = {
'node_modules/(?!(@react-native|react-native|@react-navigation|expo|expo-.*|@expo|@react-native-community|react-native-.*|@shopify|@better-auth|nativewind|react-native-css-interop)/)', 'node_modules/(?!(@react-native|react-native|@react-navigation|expo|expo-.*|@expo|@react-native-community|react-native-.*|@shopify|@better-auth|nativewind|react-native-css-interop)/)',
], ],
transform: { transform: {
'^.+\\.(js|jsx|ts|tsx)$': ['ts-jest', tsjPresets.defaults], '^.+\\.(js|jsx)$': 'babel-jest',
'^.+\\.(ts|tsx)$': ['ts-jest', {
tsconfig: {
jsx: 'react',
esModuleInterop: true,
allowSyntheticDefaultImports: true,
},
}],
}, },
testMatch: ['**/__tests__/**/*.(test|spec).(js|jsx|ts|tsx)', '**/*.(test|spec).(js|jsx|ts|tsx)'], testMatch: ['**/__tests__/**/*.(test|spec).(js|jsx|ts|tsx)', '**/*.(test|spec).(js|jsx|ts|tsx)'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
@ -26,13 +31,4 @@ module.exports = {
'!**/jest.setup.js', '!**/jest.setup.js',
'!**/metro.config.js', '!**/metro.config.js',
], ],
globals: {
'ts-jest': {
tsconfig: {
jsx: 'react',
esModuleInterop: true,
allowSyntheticDefaultImports: true,
},
},
},
} }

View File

@ -1,7 +1,8 @@
import '@testing-library/jest-native/extend-expect' require('@testing-library/jest-native/extend-expect')
// Mock react-native modules // Mock react-native modules
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper') // Note: NativeAnimatedHelper may not be needed in newer React Native versions
// jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper')
// Mock expo modules // Mock expo modules
jest.mock('expo-constants', () => ({ jest.mock('expo-constants', () => ({

View File

@ -87,6 +87,10 @@
"@babel/cli": "^7.28.3", "@babel/cli": "^7.28.3",
"@babel/core": "^7.28.5", "@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5", "@babel/preset-env": "^7.28.5",
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react": "^12.0.0",
"@testing-library/react-native": "^12.8.1",
"@types/jest": "^29.5.14",
"@types/react": "^19.2.1", "@types/react": "^19.2.1",
"@types/react-native": "^0.72.8", "@types/react-native": "^0.72.8",
"@typescript-eslint/eslint-plugin": "^8.33.0", "@typescript-eslint/eslint-plugin": "^8.33.0",
@ -105,16 +109,13 @@
"eslint-plugin-tailwindcss": "^3.18.0", "eslint-plugin-tailwindcss": "^3.18.0",
"eslint-plugin-unicorn": "^59.0.1", "eslint-plugin-unicorn": "^59.0.1",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"jest": "^29.7.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.12", "prettier-plugin-tailwindcss": "^0.6.12",
"tailwindcss": "3.4.4",
"typescript": "~5.9.2",
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^12.8.1",
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"react-test-renderer": "19.1.0", "react-test-renderer": "19.1.0",
"ts-jest": "^29.2.5" "tailwindcss": "3.4.4",
"ts-jest": "^29.2.5",
"typescript": "~5.9.2"
}, },
"private": true "private": true
} }