dify/web/app/components/tools/provider/__tests__/empty.spec.tsx
Jingyi 9b74df21d0
feat(web): refine onboarding UI (#37433)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: hjlarry <hjlarry@163.com>
Co-authored-by: fatelei <fatelei@gmail.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Xiyuan Chen <52963600+GareArc@users.noreply.github.com>
Co-authored-by: gigglewang <gigglewang@dify.ai>
Co-authored-by: Yunlu Wen <yunlu.wen@dify.ai>
Co-authored-by: chariri <w@chariri.moe>
Co-authored-by: Evan <2869018789@qq.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
2026-06-15 08:47:15 +00:00

187 lines
6.6 KiB
TypeScript

import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
// Import the mock to control it in tests
import useTheme from '@/hooks/use-theme'
import { ToolTypeEnum } from '../../../workflow/block-selector/types'
import Empty from '../empty'
// Mock useTheme hook
vi.mock('@/hooks/use-theme', () => ({
default: vi.fn(() => ({ theme: 'light' })),
}))
describe('Empty', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(useTheme).mockReturnValue({ theme: 'light' } as ReturnType<typeof useTheme>)
})
// Tests for basic rendering scenarios
describe('Rendering', () => {
it('should render without crashing', () => {
render(<Empty />)
expect(screen.getByText('No tools available')).toBeInTheDocument()
})
it('should render placeholder icon', () => {
render(<Empty />)
// NoToolPlaceholder should be rendered
const container = document.querySelector('.flex.flex-col')
expect(container).toBeInTheDocument()
})
it('should render fallback title when no type provided', () => {
render(<Empty />)
expect(screen.getByText('No tools available')).toBeInTheDocument()
})
})
// Tests for different type prop values
describe('Type Props', () => {
it('should render with Custom type and include link to Swagger API as Tool', () => {
render(<Empty type={ToolTypeEnum.Custom} />)
const link = document.querySelector('a[href="/integrations/tools/api"]')
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('target', '_blank')
})
it('should render with MCP type and include link to the MCP route', () => {
render(<Empty type={ToolTypeEnum.MCP} />)
const link = document.querySelector('a[href="/integrations/tools/mcp"]')
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('target', '_blank')
})
it('should render arrow icon for types with links', () => {
render(<Empty type={ToolTypeEnum.Custom} />)
// Check for RiArrowRightUpLine icon (has class size-3)
const arrowIcon = document.querySelector('.size-3')
expect(arrowIcon).toBeInTheDocument()
})
it('should not render link for BuiltIn type', () => {
render(<Empty type={ToolTypeEnum.BuiltIn} />)
const link = document.querySelector('a')
expect(link).not.toBeInTheDocument()
})
it('should render workflow empty guide with studio and docs links', () => {
render(<Empty type={ToolTypeEnum.Workflow} />)
expect(screen.getByText('tools.workflowToolEmpty.title')).toBeInTheDocument()
expect(screen.getByText('tools.workflowToolEmpty.description')).toBeInTheDocument()
expect(screen.getByText('tools.workflowToolEmpty.step1')).toBeInTheDocument()
expect(screen.getByText('tools.workflowToolEmpty.step2')).toBeInTheDocument()
expect(screen.getByText('tools.workflowToolEmpty.step3')).toBeInTheDocument()
expect(screen.getByRole('link', { name: /tools\.workflowToolEmpty\.goToStudio/i })).toHaveAttribute('href', '/apps')
expect(screen.getByRole('link', { name: /tools\.workflowToolEmpty\.learnMore/i })).toHaveAttribute('target', '_blank')
expect(screen.getByRole('link', { name: /tools\.workflowToolEmpty\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/workspace/tools#workflow-tool')
})
})
// Tests for isAgent prop
describe('isAgent Prop', () => {
it('should render as agent without link', () => {
render(<Empty type={ToolTypeEnum.Custom} isAgent />)
// When isAgent is true, no link should be rendered
const link = document.querySelector('a')
expect(link).not.toBeInTheDocument()
})
it('should not render tip text when isAgent is true', () => {
render(<Empty type={ToolTypeEnum.Custom} isAgent />)
// Arrow icon should not be present when isAgent is true
const arrowIcon = document.querySelector('.size-3')
expect(arrowIcon).not.toBeInTheDocument()
})
})
// Tests for theme-based styling
describe('Theme Support', () => {
it('should not apply invert class in light theme', () => {
vi.mocked(useTheme).mockReturnValue({ theme: 'light' } as ReturnType<typeof useTheme>)
render(<Empty />)
// The NoToolPlaceholder should not have 'invert' class in light mode
// We check the first svg or container within the component
const placeholder = document.querySelector('.flex.flex-col > *:first-child')
expect(placeholder).not.toHaveClass('invert')
})
it('should apply invert class in dark theme', () => {
vi.mocked(useTheme).mockReturnValue({ theme: 'dark' } as ReturnType<typeof useTheme>)
render(<Empty />)
// The NoToolPlaceholder should have 'invert' class in dark mode
const placeholder = document.querySelector('.invert')
expect(placeholder).toBeInTheDocument()
})
})
// Tests for translation key handling
describe('Translation Keys', () => {
it('should use correct translation namespace for tools', () => {
render(<Empty type={ToolTypeEnum.Custom} />)
// The component should render translation keys with 'tools' namespace
// Translation mock returns the key itself
expect(screen.getByText(/addToolModal\.custom\.title/i)).toBeInTheDocument()
})
it('should render tip text for types with hasTitle', () => {
render(<Empty type={ToolTypeEnum.Custom} />)
// Should show the tip text with translation key
expect(screen.getByText(/addToolModal\.custom\.tip/i)).toBeInTheDocument()
})
})
// Tests for edge cases
describe('Edge Cases', () => {
it('should handle undefined type gracefully', () => {
render(<Empty type={undefined} />)
expect(screen.getByText('No tools available')).toBeInTheDocument()
})
it('should handle All type without link', () => {
render(<Empty type={ToolTypeEnum.All} />)
const link = document.querySelector('a')
expect(link).not.toBeInTheDocument()
})
})
// Tests for link styling
describe('Link Styling', () => {
it('should apply hover styling classes to link', () => {
render(<Empty type={ToolTypeEnum.Custom} />)
const link = document.querySelector('a')
expect(link).toHaveClass('cursor-pointer')
expect(link).toHaveClass('hover:text-text-accent')
})
it('should render div instead of link when hasLink is false', () => {
render(<Empty type={ToolTypeEnum.BuiltIn} />)
// No anchor tags should be rendered
const anchors = document.querySelectorAll('a')
expect(anchors.length).toBe(0)
})
})
})