524 lines
14 KiB
TypeScript
524 lines
14 KiB
TypeScript
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 }) => (
|
|
<View>{children}</View>
|
|
),
|
|
}))
|
|
|
|
// 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) => (
|
|
<View testID="dynamic-form">
|
|
<Text onPress={() => onSubmit({})}>Submit Form</Text>
|
|
</View>
|
|
)),
|
|
}
|
|
})
|
|
|
|
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<typeof useTemplateActions>
|
|
const mockUseTemplateDetail = useTemplateDetail as jest.MockedFunction<typeof useTemplateDetail>
|
|
const mockUseTemplateGenerations = useTemplateGenerations as jest.MockedFunction<typeof useTemplateGenerations>
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
// 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(<TemplateDetailScreen />)
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
// 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(<TemplateDetailScreen />)
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
// 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(<TemplateDetailScreen />)
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
// 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(<TemplateDetailScreen />)
|
|
|
|
// 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(<TemplateDetailScreen />)
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
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(<TemplateDetailScreen />)
|
|
|
|
expect(mockExecuteDetail).toHaveBeenCalledWith({ id: 'test-template-id' })
|
|
expect(mockExecuteGenerations).toHaveBeenCalledWith({
|
|
templateId: 'test-template-id',
|
|
page: 1,
|
|
limit: 20,
|
|
})
|
|
})
|
|
})
|
|
})
|