mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 01:18:05 +08:00
refactor(tool-selector): remove unused components and consolidate import (#31018)
Co-authored-by: CodingOnStar <hanxujiang@dify.ai>
This commit is contained in:
@ -0,0 +1,259 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Theme } from '@/types/app'
|
||||
import IconWithTooltip from './icon-with-tooltip'
|
||||
|
||||
// Mock Tooltip component
|
||||
vi.mock('@/app/components/base/tooltip', () => ({
|
||||
default: ({
|
||||
children,
|
||||
popupContent,
|
||||
popupClassName,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
popupContent?: string
|
||||
popupClassName?: string
|
||||
}) => (
|
||||
<div data-testid="tooltip" data-popup-content={popupContent} data-popup-classname={popupClassName}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock icon components
|
||||
const MockLightIcon = ({ className }: { className?: string }) => (
|
||||
<div data-testid="light-icon" className={className}>Light Icon</div>
|
||||
)
|
||||
|
||||
const MockDarkIcon = ({ className }: { className?: string }) => (
|
||||
<div data-testid="dark-icon" className={className}>Dark Icon</div>
|
||||
)
|
||||
|
||||
describe('IconWithTooltip', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Tooltip wrapper', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
popupContent="Test tooltip"
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-popup-content', 'Test tooltip')
|
||||
})
|
||||
|
||||
it('should apply correct popupClassName to Tooltip', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
const tooltip = screen.getByTestId('tooltip')
|
||||
expect(tooltip).toHaveAttribute('data-popup-classname')
|
||||
expect(tooltip.getAttribute('data-popup-classname')).toContain('border-components-panel-border')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Theme Handling', () => {
|
||||
it('should render light icon when theme is light', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('light-icon')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('dark-icon')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render dark icon when theme is dark', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.dark}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('dark-icon')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('light-icon')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render light icon when theme is system (not dark)', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={'system' as Theme}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
// When theme is not 'dark', it should use light icon
|
||||
expect(screen.getByTestId('light-icon')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props', () => {
|
||||
it('should apply custom className to icon', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
className="custom-class"
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
const icon = screen.getByTestId('light-icon')
|
||||
expect(icon).toHaveClass('custom-class')
|
||||
})
|
||||
|
||||
it('should apply default h-5 w-5 class to icon', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
const icon = screen.getByTestId('light-icon')
|
||||
expect(icon).toHaveClass('h-5')
|
||||
expect(icon).toHaveClass('w-5')
|
||||
})
|
||||
|
||||
it('should merge custom className with default classes', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
className="ml-2"
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
const icon = screen.getByTestId('light-icon')
|
||||
expect(icon).toHaveClass('h-5')
|
||||
expect(icon).toHaveClass('w-5')
|
||||
expect(icon).toHaveClass('ml-2')
|
||||
})
|
||||
|
||||
it('should pass popupContent to Tooltip', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
popupContent="Custom tooltip content"
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute(
|
||||
'data-popup-content',
|
||||
'Custom tooltip content',
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle undefined popupContent', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Memoization', () => {
|
||||
it('should be wrapped with React.memo', () => {
|
||||
// The component is exported as React.memo(IconWithTooltip)
|
||||
expect(IconWithTooltip).toBeDefined()
|
||||
// Check if it's a memo component
|
||||
expect(typeof IconWithTooltip).toBe('object')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Container Structure', () => {
|
||||
it('should render icon inside flex container', () => {
|
||||
const { container } = render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
const flexContainer = container.querySelector('.flex.shrink-0.items-center.justify-center')
|
||||
expect(flexContainer).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty className', () => {
|
||||
render(
|
||||
<IconWithTooltip
|
||||
className=""
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('light-icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle long popupContent', () => {
|
||||
const longContent = 'A'.repeat(500)
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
popupContent={longContent}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-popup-content', longContent)
|
||||
})
|
||||
|
||||
it('should handle special characters in popupContent', () => {
|
||||
const specialContent = '<script>alert("xss")</script> & "quotes"'
|
||||
render(
|
||||
<IconWithTooltip
|
||||
theme={Theme.light}
|
||||
BadgeIconLight={MockLightIcon}
|
||||
BadgeIconDark={MockDarkIcon}
|
||||
popupContent={specialContent}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-popup-content', specialContent)
|
||||
})
|
||||
})
|
||||
})
|
||||
205
web/app/components/plugins/base/badges/partner.spec.tsx
Normal file
205
web/app/components/plugins/base/badges/partner.spec.tsx
Normal file
@ -0,0 +1,205 @@
|
||||
import type { ComponentProps } from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Theme } from '@/types/app'
|
||||
import Partner from './partner'
|
||||
|
||||
// Mock useTheme hook
|
||||
const mockUseTheme = vi.fn()
|
||||
vi.mock('@/hooks/use-theme', () => ({
|
||||
default: () => mockUseTheme(),
|
||||
}))
|
||||
|
||||
// Mock IconWithTooltip to directly test Partner's behavior
|
||||
type IconWithTooltipProps = ComponentProps<typeof import('./icon-with-tooltip').default>
|
||||
const mockIconWithTooltip = vi.fn()
|
||||
vi.mock('./icon-with-tooltip', () => ({
|
||||
default: (props: IconWithTooltipProps) => {
|
||||
mockIconWithTooltip(props)
|
||||
const { theme, BadgeIconLight, BadgeIconDark, className, popupContent } = props
|
||||
const isDark = theme === Theme.dark
|
||||
const Icon = isDark ? BadgeIconDark : BadgeIconLight
|
||||
return (
|
||||
<div data-testid="icon-with-tooltip" data-popup-content={popupContent} data-theme={theme}>
|
||||
<Icon className={className} data-testid={isDark ? 'partner-dark-icon' : 'partner-light-icon'} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock Partner icons
|
||||
vi.mock('@/app/components/base/icons/src/public/plugins/PartnerDark', () => ({
|
||||
default: ({ className, ...rest }: { className?: string }) => (
|
||||
<div data-testid="partner-dark-icon" className={className} {...rest}>PartnerDark</div>
|
||||
),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/icons/src/public/plugins/PartnerLight', () => ({
|
||||
default: ({ className, ...rest }: { className?: string }) => (
|
||||
<div data-testid="partner-light-icon" className={className} {...rest}>PartnerLight</div>
|
||||
),
|
||||
}))
|
||||
|
||||
describe('Partner', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockUseTheme.mockReturnValue({ theme: Theme.light })
|
||||
mockIconWithTooltip.mockClear()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<Partner text="Partner Tip" />)
|
||||
|
||||
expect(screen.getByTestId('icon-with-tooltip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call useTheme hook', () => {
|
||||
render(<Partner text="Partner" />)
|
||||
|
||||
expect(mockUseTheme).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should pass text prop as popupContent to IconWithTooltip', () => {
|
||||
render(<Partner text="This is a partner" />)
|
||||
|
||||
expect(screen.getByTestId('icon-with-tooltip')).toHaveAttribute(
|
||||
'data-popup-content',
|
||||
'This is a partner',
|
||||
)
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ popupContent: 'This is a partner' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should pass theme from useTheme to IconWithTooltip', () => {
|
||||
mockUseTheme.mockReturnValue({ theme: Theme.light })
|
||||
render(<Partner text="Partner" />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ theme: Theme.light }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should render light icon in light theme', () => {
|
||||
mockUseTheme.mockReturnValue({ theme: Theme.light })
|
||||
render(<Partner text="Partner" />)
|
||||
|
||||
expect(screen.getByTestId('partner-light-icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render dark icon in dark theme', () => {
|
||||
mockUseTheme.mockReturnValue({ theme: Theme.dark })
|
||||
render(<Partner text="Partner" />)
|
||||
|
||||
expect(screen.getByTestId('partner-dark-icon')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props', () => {
|
||||
it('should pass className to IconWithTooltip', () => {
|
||||
render(<Partner className="custom-class" text="Partner" />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ className: 'custom-class' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should pass correct BadgeIcon components to IconWithTooltip', () => {
|
||||
render(<Partner text="Partner" />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
BadgeIconLight: expect.any(Function),
|
||||
BadgeIconDark: expect.any(Function),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Theme Handling', () => {
|
||||
it('should handle light theme correctly', () => {
|
||||
mockUseTheme.mockReturnValue({ theme: Theme.light })
|
||||
render(<Partner text="Partner" />)
|
||||
|
||||
expect(mockUseTheme).toHaveBeenCalled()
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ theme: Theme.light }),
|
||||
)
|
||||
expect(screen.getByTestId('partner-light-icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle dark theme correctly', () => {
|
||||
mockUseTheme.mockReturnValue({ theme: Theme.dark })
|
||||
render(<Partner text="Partner" />)
|
||||
|
||||
expect(mockUseTheme).toHaveBeenCalled()
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ theme: Theme.dark }),
|
||||
)
|
||||
expect(screen.getByTestId('partner-dark-icon')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass updated theme when theme changes', () => {
|
||||
mockUseTheme.mockReturnValue({ theme: Theme.light })
|
||||
const { rerender } = render(<Partner text="Partner" />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ theme: Theme.light }),
|
||||
)
|
||||
|
||||
mockIconWithTooltip.mockClear()
|
||||
mockUseTheme.mockReturnValue({ theme: Theme.dark })
|
||||
rerender(<Partner text="Partner" />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ theme: Theme.dark }),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty text', () => {
|
||||
render(<Partner text="" />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ popupContent: '' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle long text', () => {
|
||||
const longText = 'A'.repeat(500)
|
||||
render(<Partner text={longText} />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ popupContent: longText }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle special characters in text', () => {
|
||||
const specialText = '<script>alert("xss")</script>'
|
||||
render(<Partner text={specialText} />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ popupContent: specialText }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle undefined className', () => {
|
||||
render(<Partner text="Partner" />)
|
||||
|
||||
expect(mockIconWithTooltip).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ className: undefined }),
|
||||
)
|
||||
})
|
||||
|
||||
it('should always call useTheme to get current theme', () => {
|
||||
render(<Partner text="Partner 1" />)
|
||||
expect(mockUseTheme).toHaveBeenCalledTimes(1)
|
||||
|
||||
mockUseTheme.mockClear()
|
||||
render(<Partner text="Partner 2" />)
|
||||
expect(mockUseTheme).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user