import React from 'react' import { render, fireEvent, waitFor } from '@testing-library/react-native' import { View, Text } from 'react-native' import TemplateDetailScreen from './templateDetail' // Mock expo-router jest.mock('expo-router', () => ({ useRouter: jest.fn(() => ({ back: jest.fn(), push: jest.fn(), })), useLocalSearchParams: jest.fn(() => ({ id: 'test-template-id' })), })) // Mock react-i18next jest.mock('react-i18next', () => ({ useTranslation: jest.fn(() => ({ t: (key: string) => key, })), })) // Mock expo-status-bar jest.mock('expo-status-bar', () => ({ StatusBar: 'StatusBar', })) // Mock react-native-safe-area-context jest.mock('react-native-safe-area-context', () => ({ SafeAreaView: ({ children }: { children: React.ReactNode }) => ( {children} ), })) // Mock components jest.mock('@/components/icon', () => ({ LeftArrowIcon: () => null, })) jest.mock('@/components/SearchResultsGrid', () => ({ __esModule: true, default: () => null, })) jest.mock('@/components/DynamicForm', () => { const { View, Text } = require('react-native') return { __esModule: true, DynamicForm: React.forwardRef(({ formSchema, onSubmit, onOpenDrawer }: any, ref: any) => ( onSubmit({})}>Submit Form )), } }) jest.mock('@/components/ui/Toast', () => ({ __esModule: true, default: { show: jest.fn(), showLoading: jest.fn(), hideLoading: jest.fn(), }, })) jest.mock('@/components/drawer/UploadReferenceImageDrawer', () => ({ __esModule: true, default: () => null, })) // Mock hooks jest.mock('@/hooks', () => ({ useTemplateActions: jest.fn(() => ({ runTemplate: jest.fn().mockResolvedValue({ generationId: 'gen-123' }), })), useTemplateDetail: jest.fn(() => ({ data: { id: 'test-template-id', title: 'Test Template', titleEn: 'Test Template', formSchema: { startNodes: [ { id: 'node_1', type: 'text', data: { label: '文本节点', text: '默认文本' }, }, ], }, }, loading: false, error: null, execute: jest.fn(), })), useTemplateGenerations: jest.fn(() => ({ generations: [], loading: false, execute: jest.fn(), })), })) import { useTemplateActions, useTemplateDetail, useTemplateGenerations } from '@/hooks' import Toast from '@/components/ui/Toast' const mockUseTemplateActions = useTemplateActions as jest.MockedFunction const mockUseTemplateDetail = useTemplateDetail as jest.MockedFunction const mockUseTemplateGenerations = useTemplateGenerations as jest.MockedFunction describe('TemplateDetail Screen', () => { beforeEach(() => { jest.clearAllMocks() }) describe('Loading State', () => { it('should show loading indicator when template is loading', () => { mockUseTemplateDetail.mockReturnValue({ data: undefined, loading: true, error: null, execute: jest.fn(), } as any) const { getByTestId } = render() // Should show loading state expect(mockUseTemplateDetail).toHaveBeenCalled() }) it('should not show loading when template data is loaded', () => { mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', titleEn: 'Test Template', }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: jest.fn(), } as any) const { queryByText } = render() expect(queryByText('加载中...')).toBeNull() }) }) describe('Error State', () => { it('should show error message when template loading fails', () => { mockUseTemplateDetail.mockReturnValue({ data: undefined, loading: false, error: { message: 'Failed to load' }, execute: jest.fn(), } as any) const { getByText } = render() expect(getByText('加载失败,请返回重试')).toBeTruthy() }) it('should show retry button on error', () => { mockUseTemplateDetail.mockReturnValue({ data: undefined, loading: false, error: { message: 'Failed to load' }, execute: jest.fn(), } as any) const { getByText } = render() expect(getByText('返回')).toBeTruthy() }) }) describe('Template Display', () => { it('should display template title', () => { const mockData = { id: 'test-id', title: 'Test Template', titleEn: 'Test Template', } mockUseTemplateDetail.mockReturnValue({ data: mockData, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: jest.fn(), } as any) const { getAllByText } = render() // Title appears in both header and main title section const titles = getAllByText('Test Template') expect(titles.length).toBeGreaterThan(0) }) it('should display start creating button when no formSchema', () => { mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', formSchema: { startNodes: [], }, }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: jest.fn(), } as any) const { getByText } = render() expect(getByText('templateDetail.startCreating')).toBeTruthy() }) it('should display generations section', () => { mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: jest.fn(), } as any) const { getByText } = render() expect(getByText('templateDetail.generations')).toBeTruthy() }) }) describe('DynamicForm Integration', () => { it('should show form inline when formSchema exists', () => { mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', formSchema: { startNodes: [ { id: 'node_1', type: 'text', data: { label: '文本' }, }, ], }, }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: jest.fn(), } as any) const { getByTestId } = render() // Form should be displayed inline expect(getByTestId('dynamic-form')).toBeTruthy() }) it('should navigate to generateVideo when start creating is pressed with no formSchema', () => { const mockPush = jest.fn() const mockRouter = { push: mockPush, back: jest.fn() } // Update the router mock jest.spyOn(require('expo-router'), 'useRouter').mockReturnValue(mockRouter) mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', formSchema: { startNodes: [], }, }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: jest.fn(), } as any) const { getByText } = render() const startButton = getByText('templateDetail.startCreating') fireEvent.press(startButton) // Should have pushed to generateVideo route expect(mockPush).toHaveBeenCalled() }) it('should handle form submission correctly', async () => { const mockRunTemplate = jest.fn().mockResolvedValue({ generationId: 'gen-123', }) mockUseTemplateActions.mockReturnValue({ runTemplate: mockRunTemplate, loading: false, error: null, } as any) mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', formSchema: { startNodes: [ { id: 'node_1', type: 'text', data: { label: '文本' }, }, ], }, }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: jest.fn(), } as any) const { getByTestId, getByText } = render() // Form is shown inline const form = getByTestId('dynamic-form') expect(form).toBeTruthy() // Submit form via the mocked submit button const submitButton = getByText('Submit Form') fireEvent.press(submitButton) await waitFor(() => { expect(mockRunTemplate).toHaveBeenCalledWith({ templateId: 'test-template-id', data: {}, }) expect(Toast.show).toHaveBeenCalledWith({ title: 'templateDetail.generationStarted', }) }) }) it('should handle form submission errors', async () => { const mockRunTemplate = jest.fn().mockResolvedValue({ error: { message: 'Submission failed' }, }) mockUseTemplateActions.mockReturnValue({ runTemplate: mockRunTemplate, loading: false, error: null, } as any) mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', formSchema: { startNodes: [ { id: 'node_1', type: 'text', data: { label: '文本' }, }, ], }, }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: jest.fn(), } as any) const { getByTestId, getByText } = render() // Form is shown inline expect(getByTestId('dynamic-form')).toBeTruthy() const submitButton = getByText('Submit Form') fireEvent.press(submitButton) await waitFor(() => { expect(mockRunTemplate).toHaveBeenCalled() }) }) }) describe('Generations Display', () => { it('should display generations when available', () => { const mockGenerations = [ { id: 'gen-1', template: { title: 'Template 1' }, resultUrl: ['http://example.com/image1.jpg'], originalUrl: 'http://example.com/thumb1.jpg', }, { id: 'gen-2', template: { titleEn: 'Template 2' }, resultUrl: ['http://example.com/image2.jpg'], originalUrl: 'http://example.com/thumb2.jpg', }, ] mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: mockGenerations as any, loading: false, execute: jest.fn(), } as any) const { getByText } = render() expect(getByText('templateDetail.generations')).toBeTruthy() }) it('should show loading indicator when loading generations', () => { mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', }, loading: false, error: null, execute: jest.fn(), } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [{ id: 'gen-1' }] as any, loading: true, execute: jest.fn(), } as any) const { getByText } = render() expect(getByText('加载中...')).toBeTruthy() }) }) describe('Data Fetching', () => { it('should fetch template detail and generations on mount', () => { const mockExecuteDetail = jest.fn() const mockExecuteGenerations = jest.fn() mockUseTemplateDetail.mockReturnValue({ data: { id: 'test-id', title: 'Test Template', }, loading: false, error: null, execute: mockExecuteDetail, } as any) mockUseTemplateGenerations.mockReturnValue({ generations: [], loading: false, execute: mockExecuteGenerations, } as any) render() expect(mockExecuteDetail).toHaveBeenCalledWith({ id: 'test-template-id' }) expect(mockExecuteGenerations).toHaveBeenCalledWith({ templateId: 'test-template-id', page: 1, limit: 20, }) }) }) })