dify/web/app/components/datasets/metadata/metadata-dataset/select-metadata.spec.tsx
Coding On Star 76b64dda52
test: add tests for dataset list (#31231)
Co-authored-by: CodingOnStar <hanxujiang@dify.ai>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
2026-01-20 13:07:00 +08:00

333 lines
9.2 KiB
TypeScript

import type { MetadataItem } from '../types'
import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { DataType } from '../types'
import SelectMetadata from './select-metadata'
type IconProps = {
className?: string
}
// Mock getIcon utility
vi.mock('../utils/get-icon', () => ({
getIcon: () => (props: IconProps) => <span data-testid="icon" className={props.className}>Icon</span>,
}))
describe('SelectMetadata', () => {
const mockList: MetadataItem[] = [
{ id: '1', name: 'field_one', type: DataType.string },
{ id: '2', name: 'field_two', type: DataType.number },
{ id: '3', name: 'field_three', type: DataType.time },
]
describe('Rendering', () => {
it('should render without crashing', () => {
const { container } = render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(container.firstChild).toBeInTheDocument()
})
it('should render search input', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(screen.getByRole('textbox')).toBeInTheDocument()
})
it('should render all metadata items', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(screen.getByText('field_one')).toBeInTheDocument()
expect(screen.getByText('field_two')).toBeInTheDocument()
expect(screen.getByText('field_three')).toBeInTheDocument()
})
it('should render new action button', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
// New action button should be present (from i18n)
expect(screen.getByText(/new/i)).toBeInTheDocument()
})
it('should render manage action button', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
// Manage action button should be present (from i18n)
expect(screen.getByText(/manage/i)).toBeInTheDocument()
})
it('should display type for each item', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(screen.getAllByText(DataType.string).length).toBeGreaterThan(0)
expect(screen.getAllByText(DataType.number).length).toBeGreaterThan(0)
expect(screen.getAllByText(DataType.time).length).toBeGreaterThan(0)
})
})
describe('Search Functionality', () => {
it('should filter items based on search query', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
const searchInput = screen.getByRole('textbox')
fireEvent.change(searchInput, { target: { value: 'one' } })
expect(screen.getByText('field_one')).toBeInTheDocument()
expect(screen.queryByText('field_two')).not.toBeInTheDocument()
expect(screen.queryByText('field_three')).not.toBeInTheDocument()
})
it('should be case insensitive search', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
const searchInput = screen.getByRole('textbox')
fireEvent.change(searchInput, { target: { value: 'ONE' } })
expect(screen.getByText('field_one')).toBeInTheDocument()
})
it('should show all items when search is cleared', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
const searchInput = screen.getByRole('textbox')
// Search for something
fireEvent.change(searchInput, { target: { value: 'one' } })
expect(screen.queryByText('field_two')).not.toBeInTheDocument()
// Clear search
fireEvent.change(searchInput, { target: { value: '' } })
expect(screen.getByText('field_two')).toBeInTheDocument()
})
it('should show no results when search matches nothing', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
const searchInput = screen.getByRole('textbox')
fireEvent.change(searchInput, { target: { value: 'xyz' } })
expect(screen.queryByText('field_one')).not.toBeInTheDocument()
expect(screen.queryByText('field_two')).not.toBeInTheDocument()
expect(screen.queryByText('field_three')).not.toBeInTheDocument()
})
})
describe('User Interactions', () => {
it('should call onSelect with item data when item is clicked', () => {
const handleSelect = vi.fn()
render(
<SelectMetadata
list={mockList}
onSelect={handleSelect}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
fireEvent.click(screen.getByText('field_one'))
expect(handleSelect).toHaveBeenCalledWith({
id: '1',
name: 'field_one',
type: DataType.string,
})
})
it('should call onNew when new button is clicked', () => {
const handleNew = vi.fn()
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={handleNew}
onManage={vi.fn()}
/>,
)
// Find and click the new action button
const newButton = screen.getByText(/new/i)
fireEvent.click(newButton.closest('div') || newButton)
expect(handleNew).toHaveBeenCalled()
})
it('should call onManage when manage button is clicked', () => {
const handleManage = vi.fn()
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={handleManage}
/>,
)
// Find and click the manage action button
const manageButton = screen.getByText(/manage/i)
fireEvent.click(manageButton.closest('div') || manageButton)
expect(handleManage).toHaveBeenCalled()
})
})
describe('Empty State', () => {
it('should render empty list', () => {
const { container } = render(
<SelectMetadata
list={[]}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(container.firstChild).toBeInTheDocument()
})
it('should still show new and manage buttons with empty list', () => {
render(
<SelectMetadata
list={[]}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(screen.getByText(/new/i)).toBeInTheDocument()
expect(screen.getByText(/manage/i)).toBeInTheDocument()
})
})
describe('Styling', () => {
it('should have correct container styling', () => {
const { container } = render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(container.firstChild).toHaveClass('w-[320px]', 'rounded-xl')
})
})
describe('Edge Cases', () => {
it('should handle single item list', () => {
const singleItem: MetadataItem[] = [
{ id: '1', name: 'only_one', type: DataType.string },
]
render(
<SelectMetadata
list={singleItem}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(screen.getByText('only_one')).toBeInTheDocument()
})
it('should handle item with long name', () => {
const longNameItem: MetadataItem[] = [
{ id: '1', name: 'this_is_a_very_long_field_name_that_might_overflow', type: DataType.string },
]
render(
<SelectMetadata
list={longNameItem}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
expect(screen.getByText('this_is_a_very_long_field_name_that_might_overflow')).toBeInTheDocument()
})
it('should handle rapid search input changes', () => {
render(
<SelectMetadata
list={mockList}
onSelect={vi.fn()}
onNew={vi.fn()}
onManage={vi.fn()}
/>,
)
const searchInput = screen.getByRole('textbox')
// Rapid typing
fireEvent.change(searchInput, { target: { value: 'f' } })
fireEvent.change(searchInput, { target: { value: 'fi' } })
fireEvent.change(searchInput, { target: { value: 'fie' } })
fireEvent.change(searchInput, { target: { value: 'fiel' } })
fireEvent.change(searchInput, { target: { value: 'field' } })
expect(screen.getByText('field_one')).toBeInTheDocument()
expect(screen.getByText('field_two')).toBeInTheDocument()
expect(screen.getByText('field_three')).toBeInTheDocument()
})
})
})