mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
test: add tests for dataset document detail (#31274)
Co-authored-by: CodingOnStar <hanxujiang@dify.ai> Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
328
web/app/components/tools/provider/custom-create-card.spec.tsx
Normal file
328
web/app/components/tools/provider/custom-create-card.spec.tsx
Normal file
@ -0,0 +1,328 @@
|
||||
import type { CustomCollectionBackend } from '../types'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthType } from '../types'
|
||||
import CustomCreateCard from './custom-create-card'
|
||||
|
||||
// Mock workspace manager state
|
||||
let mockIsWorkspaceManager = true
|
||||
|
||||
// Mock useAppContext
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
useAppContext: () => ({
|
||||
isCurrentWorkspaceManager: mockIsWorkspaceManager,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock useLocale and useDocLink
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useLocale: () => 'en-US',
|
||||
useDocLink: () => (path: string) => `https://docs.dify.ai/en/${path?.startsWith('/') ? path.slice(1) : path}`,
|
||||
}))
|
||||
|
||||
// Mock getLanguage
|
||||
vi.mock('@/i18n-config/language', () => ({
|
||||
getLanguage: () => 'en-US',
|
||||
}))
|
||||
|
||||
// Mock createCustomCollection service
|
||||
const mockCreateCustomCollection = vi.fn()
|
||||
vi.mock('@/service/tools', () => ({
|
||||
createCustomCollection: (data: CustomCollectionBackend) => mockCreateCustomCollection(data),
|
||||
}))
|
||||
|
||||
// Track modal state
|
||||
let mockModalVisible = false
|
||||
|
||||
// Mock EditCustomToolModal - complex component
|
||||
vi.mock('@/app/components/tools/edit-custom-collection-modal', () => ({
|
||||
default: ({ payload, onHide, onAdd }: {
|
||||
payload: null
|
||||
onHide: () => void
|
||||
onAdd: (data: CustomCollectionBackend) => void
|
||||
}) => {
|
||||
mockModalVisible = true
|
||||
void onAdd // Keep reference to avoid lint warning about unused param
|
||||
return (
|
||||
<div data-testid="edit-custom-collection-modal">
|
||||
<span data-testid="modal-payload">{payload === null ? 'null' : 'not-null'}</span>
|
||||
<button data-testid="close-modal" onClick={onHide}>Close</button>
|
||||
<button
|
||||
data-testid="submit-modal"
|
||||
onClick={() => {
|
||||
onAdd({
|
||||
provider: 'test-provider',
|
||||
credentials: { auth_type: AuthType.none },
|
||||
icon: { background: '#000', content: '🔧' },
|
||||
schema_type: 'json',
|
||||
schema: '{}',
|
||||
privacy_policy: '',
|
||||
custom_disclaimer: '',
|
||||
id: 'test-id',
|
||||
labels: [],
|
||||
})
|
||||
}}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock Toast
|
||||
const mockToastNotify = vi.fn()
|
||||
vi.mock('@/app/components/base/toast', () => ({
|
||||
default: {
|
||||
notify: (options: { type: string, message: string }) => mockToastNotify(options),
|
||||
},
|
||||
}))
|
||||
|
||||
describe('CustomCreateCard', () => {
|
||||
const mockOnRefreshData = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockIsWorkspaceManager = true
|
||||
mockModalVisible = false
|
||||
mockCreateCustomCollection.mockResolvedValue({})
|
||||
})
|
||||
|
||||
// Tests for conditional rendering based on workspace manager status
|
||||
describe('Workspace Manager Conditional Rendering', () => {
|
||||
it('should render card when user is workspace manager', () => {
|
||||
mockIsWorkspaceManager = true
|
||||
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Card should be visible with create text
|
||||
expect(screen.getByText(/createCustomTool/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render anything when user is not workspace manager', () => {
|
||||
mockIsWorkspaceManager = false
|
||||
|
||||
const { container } = render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Container should be empty (firstChild is null when nothing renders)
|
||||
expect(container.firstChild).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for card rendering and styling
|
||||
describe('Card Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
expect(screen.getByText(/createCustomTool/i)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render add icon', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// RiAddCircleFill icon should be present
|
||||
const iconContainer = document.querySelector('.h-10.w-10')
|
||||
expect(iconContainer).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have proper card styling', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
const card = document.querySelector('.min-h-\\[135px\\]')
|
||||
expect(card).toBeInTheDocument()
|
||||
expect(card).toHaveClass('cursor-pointer')
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for modal interaction
|
||||
describe('Modal Interaction', () => {
|
||||
it('should open modal when card is clicked', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Click on the card area (the group div)
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
expect(screen.getByTestId('edit-custom-collection-modal')).toBeInTheDocument()
|
||||
expect(mockModalVisible).toBe(true)
|
||||
})
|
||||
|
||||
it('should pass null payload to modal', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
expect(screen.getByTestId('modal-payload')).toHaveTextContent('null')
|
||||
})
|
||||
|
||||
it('should close modal when onHide is called', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Open modal
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
expect(screen.getByTestId('edit-custom-collection-modal')).toBeInTheDocument()
|
||||
|
||||
// Close modal
|
||||
fireEvent.click(screen.getByTestId('close-modal'))
|
||||
expect(screen.queryByTestId('edit-custom-collection-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for custom collection creation
|
||||
describe('Custom Collection Creation', () => {
|
||||
it('should call createCustomCollection when form is submitted', async () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Open modal
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
// Submit form
|
||||
fireEvent.click(screen.getByTestId('submit-modal'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCreateCustomCollection).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should show success toast after successful creation', async () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Open modal
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
// Submit form
|
||||
fireEvent.click(screen.getByTestId('submit-modal'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockToastNotify).toHaveBeenCalledWith({
|
||||
type: 'success',
|
||||
message: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should close modal after successful creation', async () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Open modal
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
expect(screen.getByTestId('edit-custom-collection-modal')).toBeInTheDocument()
|
||||
|
||||
// Submit form
|
||||
fireEvent.click(screen.getByTestId('submit-modal'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('edit-custom-collection-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call onRefreshData after successful creation', async () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Open modal
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
// Submit form
|
||||
fireEvent.click(screen.getByTestId('submit-modal'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnRefreshData).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass correct data to createCustomCollection', async () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Open modal
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
// Submit form
|
||||
fireEvent.click(screen.getByTestId('submit-modal'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockCreateCustomCollection).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
provider: 'test-provider',
|
||||
schema_type: 'json',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for edge cases
|
||||
describe('Edge Cases', () => {
|
||||
it('should call createCustomCollection and handle successful response', async () => {
|
||||
mockCreateCustomCollection.mockResolvedValue({ success: true })
|
||||
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Open modal
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
// Submit form
|
||||
fireEvent.click(screen.getByTestId('submit-modal'))
|
||||
|
||||
// The API should be called
|
||||
await waitFor(() => {
|
||||
expect(mockCreateCustomCollection).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// And refresh should be triggered
|
||||
await waitFor(() => {
|
||||
expect(mockOnRefreshData).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not call onRefreshData if modal is just closed without submitting', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
// Open modal
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
// Close modal without submitting
|
||||
fireEvent.click(screen.getByTestId('close-modal'))
|
||||
|
||||
expect(mockOnRefreshData).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle rapid open/close of modal', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
const cardClickArea = document.querySelector('.group.grow')
|
||||
|
||||
// Rapid open/close
|
||||
fireEvent.click(cardClickArea!)
|
||||
fireEvent.click(screen.getByTestId('close-modal'))
|
||||
fireEvent.click(cardClickArea!)
|
||||
|
||||
expect(screen.getByTestId('edit-custom-collection-modal')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for hover styling
|
||||
describe('Hover Styling', () => {
|
||||
it('should have hover styles on card', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
const card = document.querySelector('.transition-all.duration-200')
|
||||
expect(card).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have group hover styles on icon container', () => {
|
||||
render(<CustomCreateCard onRefreshData={mockOnRefreshData} />)
|
||||
|
||||
const iconContainer = document.querySelector('.group-hover\\:border-state-accent-hover-alt')
|
||||
expect(iconContainer).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
179
web/app/components/tools/provider/empty.spec.tsx
Normal file
179
web/app/components/tools/provider/empty.spec.tsx
Normal file
@ -0,0 +1,179 @@
|
||||
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 /tools?category=api', () => {
|
||||
render(<Empty type={ToolTypeEnum.Custom} />)
|
||||
|
||||
const link = document.querySelector('a[href="/tools?category=api"]')
|
||||
expect(link).toBeInTheDocument()
|
||||
expect(link).toHaveAttribute('target', '_blank')
|
||||
})
|
||||
|
||||
it('should render with MCP type and include link to /tools?category=mcp', () => {
|
||||
render(<Empty type={ToolTypeEnum.MCP} />)
|
||||
|
||||
const link = document.querySelector('a[href="/tools?category=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 h-3 w-3)
|
||||
const arrowIcon = document.querySelector('.h-3.w-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 not render link for Workflow type', () => {
|
||||
render(<Empty type={ToolTypeEnum.Workflow} />)
|
||||
|
||||
const link = document.querySelector('a')
|
||||
expect(link).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// 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('.h-3.w-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)
|
||||
})
|
||||
})
|
||||
})
|
||||
279
web/app/components/tools/provider/tool-item.spec.tsx
Normal file
279
web/app/components/tools/provider/tool-item.spec.tsx
Normal file
@ -0,0 +1,279 @@
|
||||
import type { Collection, Tool } from '../types'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import ToolItem from './tool-item'
|
||||
|
||||
// Mock useLocale hook
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useLocale: () => 'en-US',
|
||||
}))
|
||||
|
||||
// Mock getLanguage - returns key format used in TypeWithI18N (en_US, not en-US)
|
||||
vi.mock('@/i18n-config/language', () => ({
|
||||
getLanguage: () => 'en_US',
|
||||
}))
|
||||
|
||||
// Track modal visibility for assertions
|
||||
let mockModalVisible = false
|
||||
|
||||
// Mock SettingBuiltInTool modal - complex component that needs mocking
|
||||
vi.mock('@/app/components/app/configuration/config/agent/agent-tools/setting-built-in-tool', () => ({
|
||||
default: ({ onHide, collection, toolName, readonly, isBuiltIn, isModel }: {
|
||||
onHide: () => void
|
||||
collection: Collection
|
||||
toolName: string
|
||||
readonly: boolean
|
||||
isBuiltIn: boolean
|
||||
isModel: boolean
|
||||
}) => {
|
||||
mockModalVisible = true
|
||||
return (
|
||||
<div data-testid="setting-built-in-tool-modal">
|
||||
<span data-testid="modal-tool-name">{toolName}</span>
|
||||
<span data-testid="modal-collection-id">{collection.id}</span>
|
||||
<span data-testid="modal-readonly">{readonly.toString()}</span>
|
||||
<span data-testid="modal-is-builtin">{isBuiltIn.toString()}</span>
|
||||
<span data-testid="modal-is-model">{isModel.toString()}</span>
|
||||
<button data-testid="close-modal" onClick={onHide}>Close</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
describe('ToolItem', () => {
|
||||
// Factory function for creating mock collection
|
||||
const createMockCollection = (overrides?: Partial<Collection>): Collection => ({
|
||||
id: 'test-collection-id',
|
||||
name: 'test-collection',
|
||||
author: 'Test Author',
|
||||
description: { en_US: 'Test collection description', zh_Hans: '测试集合描述' },
|
||||
icon: '🔧',
|
||||
label: { en_US: 'Test Collection', zh_Hans: '测试集合' },
|
||||
type: 'builtin',
|
||||
team_credentials: {},
|
||||
is_team_authorization: false,
|
||||
allow_delete: false,
|
||||
labels: [],
|
||||
...overrides,
|
||||
})
|
||||
|
||||
// Factory function for creating mock tool
|
||||
const createMockTool = (overrides?: Partial<Tool>): Tool => ({
|
||||
name: 'test-tool',
|
||||
author: 'Test Author',
|
||||
label: {
|
||||
en_US: 'Test Tool Label',
|
||||
zh_Hans: '测试工具标签',
|
||||
},
|
||||
description: {
|
||||
en_US: 'Test tool description for testing purposes',
|
||||
zh_Hans: '测试工具描述',
|
||||
},
|
||||
parameters: [],
|
||||
labels: [],
|
||||
output_schema: {},
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const defaultProps = {
|
||||
collection: createMockCollection(),
|
||||
tool: createMockTool(),
|
||||
isBuiltIn: true,
|
||||
isModel: false,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockModalVisible = false
|
||||
})
|
||||
|
||||
// Tests for basic rendering
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
expect(screen.getByText('Test Tool Label')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display tool label in current language', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
expect(screen.getByText('Test Tool Label')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display tool description in current language', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
expect(screen.getByText('Test tool description for testing purposes')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have cursor-pointer class by default', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
const card = document.querySelector('.cursor-pointer')
|
||||
expect(card).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have correct card styling', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl.border-\\[0\\.5px\\]')
|
||||
expect(card).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for disabled state
|
||||
describe('Disabled State', () => {
|
||||
it('should apply disabled styles when disabled is true', () => {
|
||||
render(<ToolItem {...defaultProps} disabled />)
|
||||
|
||||
const card = document.querySelector('.opacity-50')
|
||||
expect(card).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have cursor-not-allowed when disabled', () => {
|
||||
render(<ToolItem {...defaultProps} disabled />)
|
||||
|
||||
const card = document.querySelector('.\\!cursor-not-allowed')
|
||||
expect(card).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not open modal when clicking disabled card', () => {
|
||||
render(<ToolItem {...defaultProps} disabled />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
fireEvent.click(card!)
|
||||
|
||||
expect(screen.queryByTestId('setting-built-in-tool-modal')).not.toBeInTheDocument()
|
||||
expect(mockModalVisible).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for click interaction and modal
|
||||
describe('Click Interaction', () => {
|
||||
it('should open detail modal on click', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
fireEvent.click(card!)
|
||||
|
||||
expect(screen.getByTestId('setting-built-in-tool-modal')).toBeInTheDocument()
|
||||
expect(mockModalVisible).toBe(true)
|
||||
})
|
||||
|
||||
it('should pass correct props to modal', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
fireEvent.click(card!)
|
||||
|
||||
expect(screen.getByTestId('modal-tool-name')).toHaveTextContent('test-tool')
|
||||
expect(screen.getByTestId('modal-collection-id')).toHaveTextContent('test-collection-id')
|
||||
expect(screen.getByTestId('modal-readonly')).toHaveTextContent('true')
|
||||
expect(screen.getByTestId('modal-is-builtin')).toHaveTextContent('true')
|
||||
expect(screen.getByTestId('modal-is-model')).toHaveTextContent('false')
|
||||
})
|
||||
|
||||
it('should close modal when onHide is called', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
// Open modal
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
fireEvent.click(card!)
|
||||
expect(screen.getByTestId('setting-built-in-tool-modal')).toBeInTheDocument()
|
||||
|
||||
// Close modal
|
||||
fireEvent.click(screen.getByTestId('close-modal'))
|
||||
expect(screen.queryByTestId('setting-built-in-tool-modal')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for different prop combinations
|
||||
describe('Props Variations', () => {
|
||||
it('should pass isBuiltIn=false to modal when not built-in', () => {
|
||||
render(<ToolItem {...defaultProps} isBuiltIn={false} />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
fireEvent.click(card!)
|
||||
|
||||
expect(screen.getByTestId('modal-is-builtin')).toHaveTextContent('false')
|
||||
})
|
||||
|
||||
it('should pass isModel=true to modal when it is a model tool', () => {
|
||||
render(<ToolItem {...defaultProps} isModel />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
fireEvent.click(card!)
|
||||
|
||||
expect(screen.getByTestId('modal-is-model')).toHaveTextContent('true')
|
||||
})
|
||||
|
||||
it('should handle tool with different collection', () => {
|
||||
const customCollection = createMockCollection({
|
||||
id: 'custom-collection',
|
||||
name: 'Custom Collection',
|
||||
})
|
||||
|
||||
render(<ToolItem {...defaultProps} collection={customCollection} />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
fireEvent.click(card!)
|
||||
|
||||
expect(screen.getByTestId('modal-collection-id')).toHaveTextContent('custom-collection')
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for edge cases
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle tool with empty description', () => {
|
||||
const toolWithEmptyDesc = createMockTool({
|
||||
description: { 'en-US': '' },
|
||||
})
|
||||
|
||||
render(<ToolItem {...defaultProps} tool={toolWithEmptyDesc} />)
|
||||
|
||||
expect(screen.getByText('Test Tool Label')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle missing language in label', () => {
|
||||
const toolWithMissingLang = createMockTool({
|
||||
label: { en_US: '', zh_Hans: '中文标签' },
|
||||
description: { en_US: '', zh_Hans: '中文描述' },
|
||||
})
|
||||
|
||||
// Should render without crashing (will show empty string for missing en_US)
|
||||
render(<ToolItem {...defaultProps} tool={toolWithMissingLang} />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
expect(card).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show description title attribute', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
const descriptionElement = screen.getByText('Test tool description for testing purposes')
|
||||
expect(descriptionElement).toHaveAttribute('title', 'Test tool description for testing purposes')
|
||||
})
|
||||
|
||||
it('should apply line-clamp-2 to description for text overflow', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
const descriptionElement = document.querySelector('.line-clamp-2')
|
||||
expect(descriptionElement).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for accessibility
|
||||
describe('Accessibility', () => {
|
||||
it('should be clickable with keyboard', () => {
|
||||
render(<ToolItem {...defaultProps} />)
|
||||
|
||||
const card = document.querySelector('.rounded-xl')
|
||||
|
||||
// The div is clickable, test that it can receive focus-like interaction
|
||||
fireEvent.click(card!)
|
||||
|
||||
expect(screen.getByTestId('setting-built-in-tool-modal')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user