Files
dify/web/app/components/app/create-app-dialog/app-list/index.spec.tsx
2026-02-13 11:56:08 +08:00

138 lines
3.8 KiB
TypeScript

import { fireEvent, render, screen } from '@testing-library/react'
import { MARKETPLACE_URL_PREFIX } from '@/config'
import { AppModeEnum } from '@/types/app'
import Apps from './index'
const mockUseExploreAppList = vi.fn()
vi.mock('ahooks', () => ({
useDebounceFn: (fn: () => void) => ({
run: () => setTimeout(fn, 0),
cancel: vi.fn(),
flush: () => fn(),
}),
}))
vi.mock('@/context/app-context', () => ({
useAppContext: () => ({ isCurrentWorkspaceEditor: true }),
}))
vi.mock('nuqs', () => ({
useQueryState: () => ['Recommended', vi.fn()],
}))
vi.mock('@/service/use-explore', () => ({
useExploreAppList: () => mockUseExploreAppList(),
}))
vi.mock('@/app/components/app/type-selector', () => ({
default: ({ value, onChange }: { value: AppModeEnum[], onChange: (value: AppModeEnum[]) => void }) => (
<button data-testid="type-selector" onClick={() => onChange([...value, 'chat' as AppModeEnum])}>{value.join(',')}</button>
),
}))
vi.mock('../app-card', () => ({
default: ({ app, onCreate }: { app: any, onCreate: () => void }) => (
<div
data-testid="app-card"
data-name={app.app.name}
onClick={onCreate}
>
{app.app.name}
</div>
),
}))
vi.mock('@/app/components/explore/create-app-modal', () => ({
default: () => <div data-testid="create-from-template-modal" />,
}))
vi.mock('@/app/components/base/toast', () => ({
default: { notify: vi.fn() },
}))
vi.mock('@/app/components/base/amplitude', () => ({
trackEvent: vi.fn(),
}))
vi.mock('@/service/apps', () => ({
importDSL: vi.fn().mockResolvedValue({ app_id: '1' }),
}))
vi.mock('@/service/explore', () => ({
fetchAppDetail: vi.fn().mockResolvedValue({
export_data: 'dsl',
mode: 'chat',
}),
}))
vi.mock('@/app/components/workflow/plugin-dependency/hooks', () => ({
usePluginDependencies: () => ({
handleCheckPluginDependencies: vi.fn(),
}),
}))
vi.mock('@/utils/app-redirection', () => ({
getRedirection: vi.fn(),
}))
vi.mock('next/navigation', () => ({
useRouter: () => ({ push: vi.fn() }),
}))
const createAppEntry = (name: string, category: string) => ({
app_id: name,
category,
app: {
id: name,
name,
icon_type: 'emoji',
icon: '🙂',
icon_background: '#000',
icon_url: null,
description: 'desc',
mode: AppModeEnum.CHAT,
},
})
describe('Apps', () => {
const defaultData = {
allList: [
createAppEntry('Alpha', 'Cat A'),
createAppEntry('Bravo', 'Cat B'),
],
categories: ['Cat A', 'Cat B'],
}
beforeEach(() => {
vi.clearAllMocks()
mockUseExploreAppList.mockReturnValue({
data: defaultData,
isLoading: false,
})
})
it('renders template cards when data is available', () => {
render(<Apps />)
expect(screen.getAllByTestId('app-card')).toHaveLength(2)
expect(screen.getByText('Alpha')).toBeInTheDocument()
expect(screen.getByText('Bravo')).toBeInTheDocument()
})
it('opens create modal when a template card is clicked', () => {
render(<Apps />)
fireEvent.click(screen.getAllByTestId('app-card')[0])
expect(screen.getByTestId('create-from-template-modal')).toBeInTheDocument()
})
it('shows no template message when list is empty', () => {
mockUseExploreAppList.mockReturnValueOnce({
data: { allList: [], categories: [] },
isLoading: false,
})
render(<Apps />)
expect(screen.getByText('app.newApp.noTemplateFound')).toBeInTheDocument()
expect(screen.getByText('app.newApp.noTemplateFoundTip')).toBeInTheDocument()
})
it('renders explore community link', () => {
render(<Apps />)
const link = screen.getByRole('link', { name: 'app.newApp.exploreCommunity' })
expect(link).toHaveAttribute('href', `${MARKETPLACE_URL_PREFIX.replace(/\/$/, '')}/templates`)
expect(link).toHaveAttribute('target', '_blank')
expect(link).toHaveAttribute('rel', 'noopener noreferrer')
})
})