dify/web/app/components/explore/try-app/index.spec.tsx

412 lines
10 KiB
TypeScript

import type { TryAppInfo } from '@/service/try-app'
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import TryApp from './index'
import { TypeEnum } from './tab'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => {
const translations: Record<string, string> = {
'tryApp.tabHeader.try': 'Try',
'tryApp.tabHeader.detail': 'Detail',
}
return translations[key] || key
},
}),
}))
const mockUseGetTryAppInfo = vi.fn()
vi.mock('@/service/use-try-app', () => ({
useGetTryAppInfo: (...args: unknown[]) => mockUseGetTryAppInfo(...args),
}))
vi.mock('./app', () => ({
default: ({ appId, appDetail }: { appId: string, appDetail: TryAppInfo }) => (
<div data-testid="app-component" data-app-id={appId} data-mode={appDetail?.mode}>
App Component
</div>
),
}))
vi.mock('./preview', () => ({
default: ({ appId, appDetail }: { appId: string, appDetail: TryAppInfo }) => (
<div data-testid="preview-component" data-app-id={appId} data-mode={appDetail?.mode}>
Preview Component
</div>
),
}))
vi.mock('./app-info', () => ({
default: ({
appId,
appDetail,
category,
className,
onCreate,
}: { appId: string, appDetail: TryAppInfo, category?: string, className?: string, onCreate: () => void }) => (
<div
data-testid="app-info-component"
data-app-id={appId}
data-category={category}
className={className}
>
<button data-testid="create-button" onClick={onCreate}>Create</button>
App Info:
{' '}
{appDetail?.name}
</div>
),
}))
const createMockAppDetail = (mode: string = 'chat'): TryAppInfo => ({
id: 'test-app-id',
name: 'Test App Name',
description: 'Test Description',
mode,
site: {
title: 'Test Site Title',
icon: '🚀',
icon_type: 'emoji',
icon_background: '#FFFFFF',
icon_url: '',
},
model_config: {
model: {
provider: 'langgenius/openai/openai',
name: 'gpt-4',
mode: 'chat',
},
dataset_configs: {
datasets: {
datasets: [],
},
},
agent_mode: {
tools: [],
},
user_input_form: [],
},
} as unknown as TryAppInfo)
describe('TryApp (main index.tsx)', () => {
beforeEach(() => {
mockUseGetTryAppInfo.mockReturnValue({
data: createMockAppDetail(),
isLoading: false,
})
})
afterEach(() => {
cleanup()
vi.clearAllMocks()
})
describe('loading state', () => {
it('renders loading when isLoading is true', () => {
mockUseGetTryAppInfo.mockReturnValue({
data: null,
isLoading: true,
})
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
expect(document.body.querySelector('[role="status"]')).toBeInTheDocument()
})
})
describe('content rendering', () => {
it('renders Tab component', async () => {
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
expect(screen.getByText('Try')).toBeInTheDocument()
expect(screen.getByText('Detail')).toBeInTheDocument()
})
})
it('renders App component by default (TRY mode)', async () => {
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
expect(document.body.querySelector('[data-testid="app-component"]')).toBeInTheDocument()
expect(document.body.querySelector('[data-testid="preview-component"]')).not.toBeInTheDocument()
})
})
it('renders AppInfo component', async () => {
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
expect(document.body.querySelector('[data-testid="app-info-component"]')).toBeInTheDocument()
})
})
it('renders close button', async () => {
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
// Find the close button (the one with RiCloseLine icon)
const buttons = document.body.querySelectorAll('button')
expect(buttons.length).toBeGreaterThan(0)
})
})
})
describe('tab switching', () => {
it('switches to Preview when Detail tab is clicked', async () => {
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
expect(screen.getByText('Detail')).toBeInTheDocument()
})
fireEvent.click(screen.getByText('Detail'))
await waitFor(() => {
expect(document.body.querySelector('[data-testid="preview-component"]')).toBeInTheDocument()
expect(document.body.querySelector('[data-testid="app-component"]')).not.toBeInTheDocument()
})
})
it('switches back to App when Try tab is clicked', async () => {
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
expect(screen.getByText('Detail')).toBeInTheDocument()
})
// First switch to Detail
fireEvent.click(screen.getByText('Detail'))
await waitFor(() => {
expect(document.body.querySelector('[data-testid="preview-component"]')).toBeInTheDocument()
})
// Then switch back to Try
fireEvent.click(screen.getByText('Try'))
await waitFor(() => {
expect(document.body.querySelector('[data-testid="app-component"]')).toBeInTheDocument()
})
})
})
describe('close functionality', () => {
it('calls onClose when close button is clicked', async () => {
const mockOnClose = vi.fn()
render(
<TryApp
appId="test-app-id"
onClose={mockOnClose}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
// Find the button with close icon
const buttons = document.body.querySelectorAll('button')
const closeButton = Array.from(buttons).find(btn =>
btn.querySelector('svg') || btn.className.includes('rounded-[10px]'),
)
expect(closeButton).toBeInTheDocument()
if (closeButton)
fireEvent.click(closeButton)
})
expect(mockOnClose).toHaveBeenCalled()
})
})
describe('create functionality', () => {
it('calls onCreate when create button in AppInfo is clicked', async () => {
const mockOnCreate = vi.fn()
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={mockOnCreate}
/>,
)
await waitFor(() => {
const createButton = document.body.querySelector('[data-testid="create-button"]')
expect(createButton).toBeInTheDocument()
if (createButton)
fireEvent.click(createButton)
})
expect(mockOnCreate).toHaveBeenCalledTimes(1)
})
})
describe('category prop', () => {
it('passes category to AppInfo when provided', async () => {
render(
<TryApp
appId="test-app-id"
category="AI Assistant"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
const appInfo = document.body.querySelector('[data-testid="app-info-component"]')
expect(appInfo).toHaveAttribute('data-category', 'AI Assistant')
})
})
it('does not pass category to AppInfo when not provided', async () => {
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
const appInfo = document.body.querySelector('[data-testid="app-info-component"]')
expect(appInfo).not.toHaveAttribute('data-category', expect.any(String))
})
})
})
describe('hook calls', () => {
it('calls useGetTryAppInfo with correct appId', () => {
render(
<TryApp
appId="my-specific-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
expect(mockUseGetTryAppInfo).toHaveBeenCalledWith('my-specific-app-id')
})
})
describe('props passing', () => {
it('passes appId to App component', async () => {
render(
<TryApp
appId="my-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
const appComponent = document.body.querySelector('[data-testid="app-component"]')
expect(appComponent).toHaveAttribute('data-app-id', 'my-app-id')
})
})
it('passes appId to Preview component when in Detail mode', async () => {
render(
<TryApp
appId="my-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
expect(screen.getByText('Detail')).toBeInTheDocument()
})
fireEvent.click(screen.getByText('Detail'))
await waitFor(() => {
const previewComponent = document.body.querySelector('[data-testid="preview-component"]')
expect(previewComponent).toHaveAttribute('data-app-id', 'my-app-id')
})
})
it('passes appId to AppInfo component', async () => {
render(
<TryApp
appId="my-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
const appInfoComponent = document.body.querySelector('[data-testid="app-info-component"]')
expect(appInfoComponent).toHaveAttribute('data-app-id', 'my-app-id')
})
})
it('passes appDetail to AppInfo component', async () => {
render(
<TryApp
appId="test-app-id"
onClose={vi.fn()}
onCreate={vi.fn()}
/>,
)
await waitFor(() => {
const appInfoComponent = document.body.querySelector('[data-testid="app-info-component"]')
expect(appInfoComponent?.textContent).toContain('Test App Name')
})
})
})
describe('TypeEnum export', () => {
it('exports TypeEnum correctly', () => {
expect(TypeEnum.TRY).toBe('try')
expect(TypeEnum.DETAIL).toBe('detail')
})
})
})