Files
dify/web/app/components/datasets/documents/index.spec.tsx
qiuqiua 9ef6b90843 feat: sync main branch (#31938)
Signed-off-by: majiayu000 <1835304752@qq.com>
Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Signed-off-by: -LAN- <laipz8200@outlook.com>
Signed-off-by: yihong0618 <zouzou0208@gmail.com>
Co-authored-by: QuantumGhost <obelisk.reg+git@gmail.com>
Co-authored-by: 盐粒 Yanli <yanli@dify.ai>
Co-authored-by: wangxiaolei <fatelei@gmail.com>
Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Cursx <33718736+Cursx@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: lif <1835304752@qq.com>
Co-authored-by: 非法操作 <hjlarry@163.com>
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: fenglin <790872612@qq.com>
Co-authored-by: qiaofenglin <qiaofenglin@baidu.com>
Co-authored-by: -LAN- <laipz8200@outlook.com>
Co-authored-by: TomoOkuyama <49631611+TomoOkuyama@users.noreply.github.com>
Co-authored-by: Tomo Okuyama <tomo.okuyama@intersystems.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: zyssyz123 <916125788@qq.com>
Co-authored-by: hj24 <mambahj24@gmail.com>
Co-authored-by: Coding On Star <447357187@qq.com>
Co-authored-by: CodingOnStar <hanxujiang@dify.ai>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
Co-authored-by: Xiangxuan Qu <fghpdf@outlook.com>
Co-authored-by: fghpdf <fghpdf@users.noreply.github.com>
Co-authored-by: coopercoder <whitetiger0127@163.com>
Co-authored-by: zhaiguangpeng <zhaiguangpeng@didiglobal.com>
Co-authored-by: Junyan Qin (Chin) <rockchinq@gmail.com>
Co-authored-by: E.G <146701565+GlobalStar117@users.noreply.github.com>
Co-authored-by: GlobalStar117 <GlobalStar117@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: CodingOnStar <hanxujiang@dify.com>
Co-authored-by: crazywoola <100913391+crazywoola@users.noreply.github.com>
Co-authored-by: heyszt <270985384@qq.com>
Co-authored-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Co-authored-by: Yeuoly <45712896+Yeuoly@users.noreply.github.com>
Co-authored-by: zxhlyh <jasonapring2015@outlook.com>
Co-authored-by: moonpanda <chuanzegao@163.com>
Co-authored-by: warlocgao <warlocgao@tencent.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: KVOJJJin <jzongcode@gmail.com>
Co-authored-by: eux <euxx@users.noreply.github.com>
Co-authored-by: bangjiehan <bangjiehan@gmail.com>
Co-authored-by: FFXN <31929997+FFXN@users.noreply.github.com>
Co-authored-by: Jyong <76649700+JohnJyong@users.noreply.github.com>
Co-authored-by: Nie Ronghua <nieronghua@sf-express.com>
Co-authored-by: JQSevenMiao <141806521+JQSevenMiao@users.noreply.github.com>
Co-authored-by: jiasiqi <jiasiqi3@tal.com>
Co-authored-by: Seokrin Taron Sung <sungsjade@gmail.com>
Co-authored-by: CrabSAMA <40541269+CrabSAMA@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: yihong <zouzou0208@gmail.com>
Co-authored-by: Joel <iamjoel007@gmail.com>
Co-authored-by: Wu Tianwei <30284043+WTW0313@users.noreply.github.com>
Co-authored-by: yessenia <yessenia.contact@gmail.com>
Co-authored-by: Jax <anobaka@qq.com>
Co-authored-by: niveshdandyan <155956228+niveshdandyan@users.noreply.github.com>
Co-authored-by: OSS Contributor <oss-contributor@example.com>
Co-authored-by: niveshdandyan <niveshdandyan@users.noreply.github.com>
Co-authored-by: Sean Kenneth Doherty <Smaster7772@gmail.com>
2026-02-04 19:04:24 +08:00

721 lines
23 KiB
TypeScript

import type { DocumentListResponse } from '@/models/datasets'
import { act, fireEvent, render, screen } from '@testing-library/react'
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
import { useProviderContext } from '@/context/provider-context'
import { DataSourceType } from '@/models/datasets'
import { useDocumentList } from '@/service/knowledge/use-document'
import useDocumentsPageState from './hooks/use-documents-page-state'
import Documents from './index'
// Type for mock selector function - use `as MockState` to bypass strict type checking in tests
type MockSelector = Parameters<typeof useDatasetDetailContextWithSelector>[0]
type MockState = Parameters<MockSelector>[0]
// Mock Next.js router
const mockPush = vi.fn()
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: mockPush,
replace: vi.fn(),
prefetch: vi.fn(),
}),
usePathname: () => '/datasets/test-dataset-id/documents',
useSearchParams: () => new URLSearchParams(),
}))
// Mock context providers
vi.mock('@/context/dataset-detail', () => ({
useDatasetDetailContextWithSelector: vi.fn((selector: (state: unknown) => unknown) => {
const mockState = {
dataset: {
id: 'test-dataset-id',
name: 'Test Dataset',
embedding_available: true,
data_source_type: DataSourceType.FILE,
runtime_mode: 'rag',
},
}
return selector(mockState as MockState)
}),
}))
vi.mock('@/context/provider-context', () => ({
useProviderContext: vi.fn(() => ({
plan: { type: 'professional' },
})),
}))
// Mock document service hooks
const mockInvalidDocumentList = vi.fn()
const mockInvalidDocumentDetail = vi.fn()
vi.mock('@/service/knowledge/use-document', () => ({
useDocumentList: vi.fn(() => ({
data: {
data: [
{
id: 'doc-1',
name: 'Document 1',
indexing_status: 'completed',
data_source_type: 'upload_file',
position: 1,
enabled: true,
},
{
id: 'doc-2',
name: 'Document 2',
indexing_status: 'indexing',
data_source_type: 'upload_file',
position: 2,
enabled: true,
},
],
total: 2,
page: 1,
limit: 10,
has_more: false,
} as DocumentListResponse,
isLoading: false,
refetch: vi.fn(),
})),
useInvalidDocumentList: vi.fn(() => mockInvalidDocumentList),
useInvalidDocumentDetail: vi.fn(() => mockInvalidDocumentDetail),
}))
// Mock segment service hooks
vi.mock('@/service/knowledge/use-segment', () => ({
useSegmentListKey: 'segment-list-key',
useChildSegmentListKey: 'child-segment-list-key',
}))
// Mock base service hooks
vi.mock('@/service/use-base', () => ({
useInvalid: vi.fn(() => vi.fn()),
}))
// Mock metadata hook
vi.mock('../metadata/hooks/use-edit-dataset-metadata', () => ({
default: vi.fn(() => ({
isShowEditModal: false,
showEditModal: vi.fn(),
hideEditModal: vi.fn(),
datasetMetaData: [],
handleAddMetaData: vi.fn(),
handleRename: vi.fn(),
handleDeleteMetaData: vi.fn(),
builtInEnabled: false,
setBuiltInEnabled: vi.fn(),
builtInMetaData: [],
})),
}))
// Mock page state hook
const mockSetSelectedIds = vi.fn()
const mockHandleInputChange = vi.fn()
const mockHandleStatusFilterChange = vi.fn()
const mockHandleStatusFilterClear = vi.fn()
const mockHandleSortChange = vi.fn()
const mockHandlePageChange = vi.fn()
const mockHandleLimitChange = vi.fn()
const mockUpdatePollingState = vi.fn()
const mockAdjustPageForTotal = vi.fn()
vi.mock('./hooks/use-documents-page-state', () => ({
default: vi.fn(() => ({
inputValue: '',
searchValue: '',
debouncedSearchValue: '',
handleInputChange: mockHandleInputChange,
statusFilterValue: 'all',
sortValue: '-created_at' as const,
normalizedStatusFilterValue: 'all',
handleStatusFilterChange: mockHandleStatusFilterChange,
handleStatusFilterClear: mockHandleStatusFilterClear,
handleSortChange: mockHandleSortChange,
currPage: 0,
limit: 10,
handlePageChange: mockHandlePageChange,
handleLimitChange: mockHandleLimitChange,
selectedIds: [] as string[],
setSelectedIds: mockSetSelectedIds,
timerCanRun: false,
updatePollingState: mockUpdatePollingState,
adjustPageForTotal: mockAdjustPageForTotal,
})),
}))
// Mock child components - these have deep dependency chains (QueryClient, API hooks, contexts)
// Mocking them allows us to test the Documents component logic in isolation
vi.mock('./components/documents-header', () => ({
default: ({
datasetId,
embeddingAvailable,
onInputChange,
onAddDocument,
onStatusFilterChange,
onStatusFilterClear,
onSortChange,
}: {
datasetId: string
dataSourceType?: string
embeddingAvailable: boolean
isFreePlan: boolean
statusFilterValue: string
sortValue: string
inputValue: string
onInputChange: (value: string) => void
onAddDocument: () => void
onStatusFilterChange: (value: string) => void
onStatusFilterClear: () => void
onSortChange: (value: string) => void
isShowEditMetadataModal: boolean
showEditMetadataModal: () => void
hideEditMetadataModal: () => void
datasetMetaData: unknown[]
builtInMetaData: unknown[]
builtInEnabled: boolean
onAddMetaData: () => void
onRenameMetaData: () => void
onDeleteMetaData: () => void
onBuiltInEnabledChange: () => void
}) => (
<div data-testid="documents-header">
<span data-testid="header-dataset-id">{datasetId}</span>
<span data-testid="header-embedding-available">{String(embeddingAvailable)}</span>
<input
data-testid="search-input"
onChange={e => onInputChange(e.target.value)}
placeholder="Search documents"
/>
<button data-testid="add-document-btn" onClick={onAddDocument}>
Add Document
</button>
<button data-testid="status-filter-btn" onClick={() => onStatusFilterChange('completed')}>
Filter Status
</button>
<button data-testid="clear-filter-btn" onClick={onStatusFilterClear}>
Clear Filter
</button>
<button data-testid="sort-btn" onClick={() => onSortChange('-updated_at')}>
Sort
</button>
</div>
),
}))
vi.mock('./components/empty-element', () => ({
default: ({ canAdd, onClick, type }: {
canAdd: boolean
onClick: () => void
type: 'sync' | 'upload'
}) => (
<div data-testid="empty-element">
<span data-testid="empty-can-add">{String(canAdd)}</span>
<span data-testid="empty-type">{type}</span>
<button data-testid="empty-add-btn" onClick={onClick}>
Add Document
</button>
</div>
),
}))
vi.mock('./components/list', () => ({
default: ({
documents,
datasetId,
onUpdate,
selectedIds,
onSelectedIdChange,
pagination,
}: {
embeddingAvailable: boolean
documents: unknown[]
datasetId: string
onUpdate: () => void
selectedIds: string[]
onSelectedIdChange: (ids: string[]) => void
statusFilterValue: string
remoteSortValue: string
pagination: {
total: number
limit: number
current: number
onChange: (page: number) => void
onLimitChange: (limit: number) => void
}
onManageMetadata: () => void
}) => (
<div data-testid="documents-list">
<span data-testid="list-dataset-id">{datasetId}</span>
<span data-testid="list-documents-count">{documents.length}</span>
<span data-testid="list-selected-count">{selectedIds.length}</span>
<span data-testid="list-total">{pagination.total}</span>
<span data-testid="list-current-page">{pagination.current}</span>
<button data-testid="update-btn" onClick={onUpdate}>
Update
</button>
<button data-testid="select-btn" onClick={() => onSelectedIdChange(['doc-1'])}>
Select Doc
</button>
<button data-testid="page-change-btn" onClick={() => pagination.onChange(1)}>
Next Page
</button>
<button data-testid="limit-change-btn" onClick={() => pagination.onLimitChange(20)}>
Change Limit
</button>
</div>
),
}))
describe('Documents', () => {
const defaultProps = {
datasetId: 'test-dataset-id',
}
beforeEach(() => {
vi.clearAllMocks()
mockPush.mockClear()
// Reset context mocks to default
vi.mocked(useDatasetDetailContextWithSelector).mockImplementation((selector: MockSelector) => {
const mockState = {
dataset: {
id: 'test-dataset-id',
name: 'Test Dataset',
embedding_available: true,
data_source_type: DataSourceType.FILE,
runtime_mode: 'rag',
},
}
return selector(mockState as MockState)
})
})
describe('Rendering', () => {
it('should render without crashing', () => {
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('documents-header')).toBeInTheDocument()
})
it('should render DocumentsHeader with correct props', () => {
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('header-dataset-id')).toHaveTextContent('test-dataset-id')
expect(screen.getByTestId('header-embedding-available')).toHaveTextContent('true')
})
it('should render document list when documents exist', () => {
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('documents-list')).toBeInTheDocument()
expect(screen.getByTestId('list-documents-count')).toHaveTextContent('2')
})
it('should render loading state when isLoading is true', () => {
vi.mocked(useDocumentList).mockReturnValueOnce({
data: undefined,
isLoading: true,
refetch: vi.fn(),
} as unknown as ReturnType<typeof useDocumentList>)
render(<Documents {...defaultProps} />)
expect(screen.queryByTestId('documents-list')).not.toBeInTheDocument()
})
it('should render empty element when no documents exist', () => {
vi.mocked(useDocumentList).mockReturnValueOnce({
data: { data: [], total: 0, page: 1, limit: 10, has_more: false },
isLoading: false,
refetch: vi.fn(),
} as unknown as ReturnType<typeof useDocumentList>)
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('empty-element')).toBeInTheDocument()
expect(screen.getByTestId('empty-can-add')).toHaveTextContent('true')
expect(screen.getByTestId('empty-type')).toHaveTextContent('upload')
})
it('should render sync type empty element for Notion data source', () => {
vi.mocked(useDatasetDetailContextWithSelector).mockImplementation((selector: MockSelector) => {
const mockState = {
dataset: {
id: 'test-dataset-id',
name: 'Test Dataset',
embedding_available: true,
data_source_type: DataSourceType.NOTION,
runtime_mode: 'rag',
},
}
return selector(mockState as MockState)
})
vi.mocked(useDocumentList).mockReturnValueOnce({
data: { data: [], total: 0, page: 1, limit: 10, has_more: false },
isLoading: false,
refetch: vi.fn(),
} as unknown as ReturnType<typeof useDocumentList>)
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('empty-type')).toHaveTextContent('sync')
})
})
describe('Props', () => {
it('should pass datasetId to child components', () => {
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('header-dataset-id')).toHaveTextContent('test-dataset-id')
})
it('should handle different datasetId', () => {
render(<Documents datasetId="different-dataset-id" />)
expect(screen.getByTestId('header-dataset-id')).toHaveTextContent('different-dataset-id')
})
})
describe('User Interactions', () => {
it('should call handleInputChange when search input changes', async () => {
render(<Documents {...defaultProps} />)
const searchInput = screen.getByTestId('search-input')
fireEvent.change(searchInput, { target: { value: 'test' } })
expect(mockHandleInputChange).toHaveBeenCalledWith('test')
})
it('should call handleStatusFilterChange when filter button is clicked', () => {
render(<Documents {...defaultProps} />)
screen.getByTestId('status-filter-btn').click()
expect(mockHandleStatusFilterChange).toHaveBeenCalledWith('completed')
})
it('should call handleStatusFilterClear when clear button is clicked', () => {
render(<Documents {...defaultProps} />)
screen.getByTestId('clear-filter-btn').click()
expect(mockHandleStatusFilterClear).toHaveBeenCalled()
})
it('should call handleSortChange when sort button is clicked', () => {
render(<Documents {...defaultProps} />)
screen.getByTestId('sort-btn').click()
expect(mockHandleSortChange).toHaveBeenCalledWith('-updated_at')
})
it('should call setSelectedIds when document is selected', () => {
render(<Documents {...defaultProps} />)
screen.getByTestId('select-btn').click()
expect(mockSetSelectedIds).toHaveBeenCalledWith(['doc-1'])
})
it('should call handlePageChange when page changes', () => {
render(<Documents {...defaultProps} />)
screen.getByTestId('page-change-btn').click()
expect(mockHandlePageChange).toHaveBeenCalledWith(1)
})
it('should call handleLimitChange when limit changes', () => {
render(<Documents {...defaultProps} />)
screen.getByTestId('limit-change-btn').click()
expect(mockHandleLimitChange).toHaveBeenCalledWith(20)
})
})
describe('Router Navigation', () => {
it('should navigate to create page when add document is clicked', () => {
render(<Documents {...defaultProps} />)
screen.getByTestId('add-document-btn').click()
expect(mockPush).toHaveBeenCalledWith('/datasets/test-dataset-id/documents/create')
})
it('should navigate to pipeline create page when dataset is rag_pipeline mode', () => {
vi.mocked(useDatasetDetailContextWithSelector).mockImplementation((selector: MockSelector) => {
const mockState = {
dataset: {
id: 'test-dataset-id',
name: 'Test Dataset',
embedding_available: true,
data_source_type: DataSourceType.FILE,
runtime_mode: 'rag_pipeline',
},
}
return selector(mockState as MockState)
})
render(<Documents {...defaultProps} />)
screen.getByTestId('add-document-btn').click()
expect(mockPush).toHaveBeenCalledWith('/datasets/test-dataset-id/documents/create-from-pipeline')
})
it('should navigate from empty element add button', () => {
vi.mocked(useDatasetDetailContextWithSelector).mockImplementation((selector: MockSelector) => {
const mockState = {
dataset: {
id: 'test-dataset-id',
name: 'Test Dataset',
embedding_available: true,
data_source_type: DataSourceType.FILE,
runtime_mode: 'rag',
},
}
return selector(mockState as MockState)
})
vi.mocked(useDocumentList).mockReturnValueOnce({
data: { data: [], total: 0, page: 1, limit: 10, has_more: false },
isLoading: false,
refetch: vi.fn(),
} as unknown as ReturnType<typeof useDocumentList>)
render(<Documents {...defaultProps} />)
screen.getByTestId('empty-add-btn').click()
expect(mockPush).toHaveBeenCalledWith('/datasets/test-dataset-id/documents/create')
})
})
describe('Side Effects and Cleanup', () => {
it('should call updatePollingState when documents response changes', () => {
render(<Documents {...defaultProps} />)
expect(mockUpdatePollingState).toHaveBeenCalled()
})
it('should call adjustPageForTotal when documents response changes', () => {
render(<Documents {...defaultProps} />)
expect(mockAdjustPageForTotal).toHaveBeenCalled()
})
})
describe('Callback Stability and Memoization', () => {
it('should call handleUpdate with invalidation functions', async () => {
render(<Documents {...defaultProps} />)
screen.getByTestId('update-btn').click()
expect(mockInvalidDocumentList).toHaveBeenCalled()
expect(mockInvalidDocumentDetail).toHaveBeenCalled()
})
it('should handle update with delayed chunk invalidation', async () => {
vi.useFakeTimers()
render(<Documents {...defaultProps} />)
screen.getByTestId('update-btn').click()
expect(mockInvalidDocumentList).toHaveBeenCalled()
expect(mockInvalidDocumentDetail).toHaveBeenCalled()
await act(async () => {
vi.advanceTimersByTime(5000)
})
vi.useRealTimers()
})
})
describe('Edge Cases and Error Handling', () => {
it('should handle undefined dataset gracefully', () => {
vi.mocked(useDatasetDetailContextWithSelector).mockImplementation((selector: MockSelector) => {
const mockState = { dataset: undefined }
return selector(mockState as MockState)
})
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('documents-header')).toBeInTheDocument()
})
it('should handle empty documents array', () => {
vi.mocked(useDocumentList).mockReturnValueOnce({
data: { data: [], total: 0, page: 1, limit: 10, has_more: false },
isLoading: false,
refetch: vi.fn(),
} as unknown as ReturnType<typeof useDocumentList>)
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('empty-element')).toBeInTheDocument()
})
it('should handle undefined documentsRes', () => {
vi.mocked(useDocumentList).mockReturnValueOnce({
data: undefined,
isLoading: false,
refetch: vi.fn(),
} as unknown as ReturnType<typeof useDocumentList>)
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('empty-element')).toBeInTheDocument()
})
it('should handle embedding not available', () => {
vi.mocked(useDatasetDetailContextWithSelector).mockImplementation((selector: MockSelector) => {
const mockState = {
dataset: {
id: 'test-dataset-id',
name: 'Test Dataset',
embedding_available: false,
data_source_type: DataSourceType.FILE,
runtime_mode: 'rag',
},
}
return selector(mockState as MockState)
})
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('header-embedding-available')).toHaveTextContent('false')
})
it('should handle free plan user', () => {
vi.mocked(useProviderContext).mockReturnValueOnce({
plan: { type: 'sandbox' },
} as ReturnType<typeof useProviderContext>)
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('documents-header')).toBeInTheDocument()
})
})
describe('Polling State', () => {
it('should enable polling when documents are indexing', () => {
vi.mocked(useDocumentsPageState).mockReturnValueOnce({
inputValue: '',
searchValue: '',
debouncedSearchValue: '',
handleInputChange: mockHandleInputChange,
statusFilterValue: 'all',
sortValue: '-created_at' as const,
normalizedStatusFilterValue: 'all',
handleStatusFilterChange: mockHandleStatusFilterChange,
handleStatusFilterClear: mockHandleStatusFilterClear,
handleSortChange: mockHandleSortChange,
currPage: 0,
limit: 10,
handlePageChange: mockHandlePageChange,
handleLimitChange: mockHandleLimitChange,
selectedIds: [] as string[],
setSelectedIds: mockSetSelectedIds,
timerCanRun: true,
updatePollingState: mockUpdatePollingState,
adjustPageForTotal: mockAdjustPageForTotal,
})
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('documents-list')).toBeInTheDocument()
})
})
describe('Pagination', () => {
it('should display correct total in list', () => {
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('list-total')).toHaveTextContent('2')
})
it('should display correct current page', () => {
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('list-current-page')).toHaveTextContent('0')
})
it('should handle page changes', () => {
vi.mocked(useDocumentsPageState).mockReturnValueOnce({
inputValue: '',
searchValue: '',
debouncedSearchValue: '',
handleInputChange: mockHandleInputChange,
statusFilterValue: 'all',
sortValue: '-created_at' as const,
normalizedStatusFilterValue: 'all',
handleStatusFilterChange: mockHandleStatusFilterChange,
handleStatusFilterClear: mockHandleStatusFilterClear,
handleSortChange: mockHandleSortChange,
currPage: 2,
limit: 10,
handlePageChange: mockHandlePageChange,
handleLimitChange: mockHandleLimitChange,
selectedIds: [] as string[],
setSelectedIds: mockSetSelectedIds,
timerCanRun: false,
updatePollingState: mockUpdatePollingState,
adjustPageForTotal: mockAdjustPageForTotal,
})
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('list-current-page')).toHaveTextContent('2')
})
})
describe('Selection State', () => {
it('should display selected count', () => {
vi.mocked(useDocumentsPageState).mockReturnValueOnce({
inputValue: '',
searchValue: '',
debouncedSearchValue: '',
handleInputChange: mockHandleInputChange,
statusFilterValue: 'all',
sortValue: '-created_at' as const,
normalizedStatusFilterValue: 'all',
handleStatusFilterChange: mockHandleStatusFilterChange,
handleStatusFilterClear: mockHandleStatusFilterClear,
handleSortChange: mockHandleSortChange,
currPage: 0,
limit: 10,
handlePageChange: mockHandlePageChange,
handleLimitChange: mockHandleLimitChange,
selectedIds: ['doc-1', 'doc-2'],
setSelectedIds: mockSetSelectedIds,
timerCanRun: false,
updatePollingState: mockUpdatePollingState,
adjustPageForTotal: mockAdjustPageForTotal,
})
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('list-selected-count')).toHaveTextContent('2')
})
})
describe('Filter and Sort State', () => {
it('should pass filter value to list', () => {
vi.mocked(useDocumentsPageState).mockReturnValueOnce({
inputValue: 'test search',
searchValue: 'test search',
debouncedSearchValue: 'test search',
handleInputChange: mockHandleInputChange,
statusFilterValue: 'completed',
sortValue: '-created_at' as const,
normalizedStatusFilterValue: 'completed',
handleStatusFilterChange: mockHandleStatusFilterChange,
handleStatusFilterClear: mockHandleStatusFilterClear,
handleSortChange: mockHandleSortChange,
currPage: 0,
limit: 10,
handlePageChange: mockHandlePageChange,
handleLimitChange: mockHandleLimitChange,
selectedIds: [] as string[],
setSelectedIds: mockSetSelectedIds,
timerCanRun: false,
updatePollingState: mockUpdatePollingState,
adjustPageForTotal: mockAdjustPageForTotal,
})
render(<Documents {...defaultProps} />)
expect(screen.getByTestId('documents-list')).toBeInTheDocument()
})
})
})