feat: integrate UI components into Generation Record page with pagination
Transform Generation Record from static detail view to dynamic list with: - useTemplateGenerations hook for data fetching - RefreshControl for pull-to-refresh functionality - LoadingState and ErrorState for proper state handling - PaginationLoader for infinite scroll - FlatList with onEndReached for pagination - Comprehensive test coverage following TDD principles Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
276
app/generationRecord.test.tsx
Normal file
276
app/generationRecord.test.tsx
Normal file
@@ -0,0 +1,276 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent, waitFor } from '@testing-library/react-native'
|
||||
import { View } from 'react-native'
|
||||
import GenerationRecordScreen from './generationRecord'
|
||||
|
||||
jest.mock('expo-router', () => ({
|
||||
useRouter: jest.fn(() => ({
|
||||
back: jest.fn(),
|
||||
push: jest.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: jest.fn(() => ({
|
||||
t: (key: string) => key,
|
||||
})),
|
||||
}))
|
||||
|
||||
jest.mock('expo-status-bar', () => ({
|
||||
StatusBar: 'StatusBar',
|
||||
}))
|
||||
|
||||
jest.mock('react-native-safe-area-context', () => ({
|
||||
SafeAreaView: ({ children }: { children: React.ReactNode }) => (
|
||||
<View>{children}</View>
|
||||
),
|
||||
}))
|
||||
|
||||
jest.mock('expo-image', () => ({
|
||||
Image: 'Image',
|
||||
}))
|
||||
|
||||
jest.mock('@/components/icon', () => ({
|
||||
LeftArrowIcon: () => null,
|
||||
DeleteIcon: () => null,
|
||||
EditIcon: () => null,
|
||||
ChangeIcon: () => null,
|
||||
WhiteStarIcon: () => null,
|
||||
}))
|
||||
|
||||
jest.mock('@/components/ui/delete-confirm-dialog', () => ({
|
||||
DeleteConfirmDialog: () => null,
|
||||
}))
|
||||
|
||||
jest.mock('@/components/LoadingState', () => ({
|
||||
__esModule: true,
|
||||
default: ({ testID }: any) => <View testID={testID} />,
|
||||
}))
|
||||
|
||||
jest.mock('@/components/ErrorState', () => ({
|
||||
__esModule: true,
|
||||
default: ({ testID }: any) => <View testID={testID} />,
|
||||
}))
|
||||
|
||||
jest.mock('@/components/PaginationLoader', () => ({
|
||||
__esModule: true,
|
||||
default: ({ testID }: any) => <View testID={testID} />,
|
||||
}))
|
||||
|
||||
jest.mock('@/components/RefreshControl', () => ({
|
||||
__esModule: true,
|
||||
default: () => null,
|
||||
}))
|
||||
|
||||
jest.mock('@/hooks', () => ({
|
||||
useTemplateGenerations: jest.fn(),
|
||||
}))
|
||||
|
||||
import { useTemplateGenerations } from '@/hooks'
|
||||
|
||||
const mockUseTemplateGenerations = useTemplateGenerations as jest.MockedFunction<typeof useTemplateGenerations>
|
||||
|
||||
describe('GenerationRecordScreen', () => {
|
||||
const mockExecute = jest.fn()
|
||||
const mockRefetch = jest.fn()
|
||||
const mockLoadMore = jest.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it('shows loading state on initial load', () => {
|
||||
mockUseTemplateGenerations.mockReturnValue({
|
||||
generations: [],
|
||||
loading: true,
|
||||
loadingMore: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: true,
|
||||
data: undefined,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<GenerationRecordScreen />)
|
||||
expect(getByTestId('loading-state')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows error state when error occurs', () => {
|
||||
mockUseTemplateGenerations.mockReturnValue({
|
||||
generations: [],
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: { message: 'Failed to load' } as any,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: false,
|
||||
data: undefined,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<GenerationRecordScreen />)
|
||||
expect(getByTestId('error-state')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders generation list when data is loaded', () => {
|
||||
const mockGenerations = [
|
||||
{
|
||||
id: '1',
|
||||
template: { title: 'Test Template' },
|
||||
resultUrl: ['https://example.com/result.jpg'],
|
||||
originalUrl: 'https://example.com/original.jpg',
|
||||
},
|
||||
]
|
||||
|
||||
mockUseTemplateGenerations.mockReturnValue({
|
||||
generations: mockGenerations as any,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: true,
|
||||
data: { data: mockGenerations } as any,
|
||||
})
|
||||
|
||||
const { getByText } = render(<GenerationRecordScreen />)
|
||||
expect(getByText('Test Template')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('shows pagination loader when loading more', () => {
|
||||
const mockGenerations = [
|
||||
{
|
||||
id: '1',
|
||||
template: { title: 'Test Template' },
|
||||
resultUrl: ['https://example.com/result.jpg'],
|
||||
},
|
||||
]
|
||||
|
||||
mockUseTemplateGenerations.mockReturnValue({
|
||||
generations: mockGenerations as any,
|
||||
loading: false,
|
||||
loadingMore: true,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: true,
|
||||
data: { data: mockGenerations } as any,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<GenerationRecordScreen />)
|
||||
expect(getByTestId('pagination-loader')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('calls execute on mount', () => {
|
||||
mockUseTemplateGenerations.mockReturnValue({
|
||||
generations: [],
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: false,
|
||||
data: undefined,
|
||||
})
|
||||
|
||||
render(<GenerationRecordScreen />)
|
||||
expect(mockExecute).toHaveBeenCalledWith({ page: 1, limit: 20 })
|
||||
})
|
||||
|
||||
it('calls refetch on pull to refresh', async () => {
|
||||
const mockGenerations = [
|
||||
{
|
||||
id: '1',
|
||||
template: { title: 'Test Template' },
|
||||
resultUrl: ['https://example.com/result.jpg'],
|
||||
},
|
||||
]
|
||||
|
||||
mockUseTemplateGenerations.mockReturnValue({
|
||||
generations: mockGenerations as any,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: true,
|
||||
data: { data: mockGenerations } as any,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<GenerationRecordScreen />)
|
||||
const flatList = getByTestId('generation-list')
|
||||
|
||||
fireEvent(flatList, 'refresh')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRefetch).toHaveBeenCalledWith({ page: 1, limit: 20 })
|
||||
})
|
||||
})
|
||||
|
||||
it('calls loadMore when reaching end of list', async () => {
|
||||
const mockGenerations = [
|
||||
{
|
||||
id: '1',
|
||||
template: { title: 'Test Template' },
|
||||
resultUrl: ['https://example.com/result.jpg'],
|
||||
},
|
||||
]
|
||||
|
||||
mockUseTemplateGenerations.mockReturnValue({
|
||||
generations: mockGenerations as any,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: true,
|
||||
data: { data: mockGenerations } as any,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<GenerationRecordScreen />)
|
||||
const flatList = getByTestId('generation-list')
|
||||
|
||||
fireEvent(flatList, 'endReached')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLoadMore).toHaveBeenCalledWith({ limit: 20 })
|
||||
})
|
||||
})
|
||||
|
||||
it('does not call loadMore when hasMore is false', async () => {
|
||||
const mockGenerations = [
|
||||
{
|
||||
id: '1',
|
||||
template: { title: 'Test Template' },
|
||||
resultUrl: ['https://example.com/result.jpg'],
|
||||
},
|
||||
]
|
||||
|
||||
mockUseTemplateGenerations.mockReturnValue({
|
||||
generations: mockGenerations as any,
|
||||
loading: false,
|
||||
loadingMore: false,
|
||||
error: null,
|
||||
execute: mockExecute,
|
||||
refetch: mockRefetch,
|
||||
loadMore: mockLoadMore,
|
||||
hasMore: false,
|
||||
data: { data: mockGenerations } as any,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<GenerationRecordScreen />)
|
||||
const flatList = getByTestId('generation-list')
|
||||
|
||||
fireEvent(flatList, 'endReached')
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLoadMore).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user