mirror of
https://github.com/langgenius/dify.git
synced 2026-02-09 23:20:12 -05:00
308 lines
9.0 KiB
TypeScript
308 lines
9.0 KiB
TypeScript
import type { ErrorDocsResponse } from '@/models/datasets'
|
|
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { retryErrorDocs } from '@/service/datasets'
|
|
import { useDatasetErrorDocs } from '@/service/knowledge/use-dataset'
|
|
import RetryButton from './index-failed'
|
|
|
|
// Mock service hooks
|
|
const mockRefetch = vi.fn()
|
|
|
|
vi.mock('@/service/knowledge/use-dataset', () => ({
|
|
useDatasetErrorDocs: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/service/datasets', () => ({
|
|
retryErrorDocs: vi.fn(),
|
|
}))
|
|
|
|
const mockUseDatasetErrorDocs = vi.mocked(useDatasetErrorDocs)
|
|
const mockRetryErrorDocs = vi.mocked(retryErrorDocs)
|
|
|
|
afterEach(() => {
|
|
cleanup()
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
// Helper to create mock query result
|
|
const createMockQueryResult = (
|
|
data: ErrorDocsResponse | undefined,
|
|
isLoading: boolean,
|
|
) => ({
|
|
data,
|
|
isLoading,
|
|
refetch: mockRefetch,
|
|
// Required query result properties
|
|
error: null,
|
|
isError: false,
|
|
isFetched: true,
|
|
isFetching: false,
|
|
isSuccess: !isLoading && !!data,
|
|
status: isLoading ? 'pending' : 'success',
|
|
dataUpdatedAt: Date.now(),
|
|
errorUpdatedAt: 0,
|
|
failureCount: 0,
|
|
failureReason: null,
|
|
errorUpdateCount: 0,
|
|
isLoadingError: false,
|
|
isPaused: false,
|
|
isPlaceholderData: false,
|
|
isPending: isLoading,
|
|
isRefetchError: false,
|
|
isRefetching: false,
|
|
isStale: false,
|
|
fetchStatus: 'idle',
|
|
promise: Promise.resolve(data as ErrorDocsResponse),
|
|
isFetchedAfterMount: true,
|
|
isInitialLoading: false,
|
|
}) as unknown as ReturnType<typeof useDatasetErrorDocs>
|
|
|
|
describe('RetryButton (IndexFailed)', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockRefetch.mockResolvedValue({})
|
|
})
|
|
|
|
describe('Rendering', () => {
|
|
it('should render nothing when loading', () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult(undefined, true),
|
|
)
|
|
|
|
const { container } = render(<RetryButton datasetId="test-dataset" />)
|
|
expect(container.firstChild).toBeNull()
|
|
})
|
|
|
|
it('should render nothing when no error documents', () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({ total: 0, data: [] }, false),
|
|
)
|
|
|
|
const { container } = render(<RetryButton datasetId="test-dataset" />)
|
|
expect(container.firstChild).toBeNull()
|
|
})
|
|
|
|
it('should render StatusWithAction when error documents exist', () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({
|
|
total: 3,
|
|
data: [
|
|
{ id: 'doc1' },
|
|
{ id: 'doc2' },
|
|
{ id: 'doc3' },
|
|
] as ErrorDocsResponse['data'],
|
|
}, false),
|
|
)
|
|
|
|
render(<RetryButton datasetId="test-dataset" />)
|
|
expect(screen.getByText(/retry/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should display error count in description', () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({
|
|
total: 5,
|
|
data: [{ id: 'doc1' }] as ErrorDocsResponse['data'],
|
|
}, false),
|
|
)
|
|
|
|
render(<RetryButton datasetId="test-dataset" />)
|
|
expect(screen.getByText(/5/)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Props', () => {
|
|
it('should pass datasetId to useDatasetErrorDocs', () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({ total: 0, data: [] }, false),
|
|
)
|
|
|
|
render(<RetryButton datasetId="my-dataset-id" />)
|
|
expect(mockUseDatasetErrorDocs).toHaveBeenCalledWith('my-dataset-id')
|
|
})
|
|
})
|
|
|
|
describe('User Interactions', () => {
|
|
it('should call retryErrorDocs when retry button is clicked', async () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({
|
|
total: 2,
|
|
data: [{ id: 'doc1' }, { id: 'doc2' }] as ErrorDocsResponse['data'],
|
|
}, false),
|
|
)
|
|
|
|
mockRetryErrorDocs.mockResolvedValue({ result: 'success' })
|
|
|
|
render(<RetryButton datasetId="test-dataset" />)
|
|
|
|
const retryButton = screen.getByText(/retry/i)
|
|
fireEvent.click(retryButton)
|
|
|
|
await waitFor(() => {
|
|
expect(mockRetryErrorDocs).toHaveBeenCalledWith({
|
|
datasetId: 'test-dataset',
|
|
document_ids: ['doc1', 'doc2'],
|
|
})
|
|
})
|
|
|
|
// Wait for all state updates to complete
|
|
await waitFor(() => {
|
|
expect(mockRefetch).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
it('should refetch error docs after successful retry', async () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({
|
|
total: 1,
|
|
data: [{ id: 'doc1' }] as ErrorDocsResponse['data'],
|
|
}, false),
|
|
)
|
|
|
|
mockRetryErrorDocs.mockResolvedValue({ result: 'success' })
|
|
|
|
render(<RetryButton datasetId="test-dataset" />)
|
|
|
|
const retryButton = screen.getByText(/retry/i)
|
|
fireEvent.click(retryButton)
|
|
|
|
await waitFor(() => {
|
|
expect(mockRefetch).toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
it('should disable button while retrying', async () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({
|
|
total: 1,
|
|
data: [{ id: 'doc1' }] as ErrorDocsResponse['data'],
|
|
}, false),
|
|
)
|
|
|
|
let resolveRetry: ((value: { result: 'success' }) => void) | undefined
|
|
mockRetryErrorDocs.mockImplementation(() => new Promise((resolve) => {
|
|
resolveRetry = resolve
|
|
}))
|
|
|
|
render(<RetryButton datasetId="test-dataset" />)
|
|
|
|
const retryButton = screen.getByText(/retry/i)
|
|
fireEvent.click(retryButton)
|
|
|
|
// Button should show disabled styling during retry
|
|
await waitFor(() => {
|
|
const button = screen.getByText(/retry/i)
|
|
expect(button).toHaveClass('cursor-not-allowed')
|
|
expect(button).toHaveClass('text-text-disabled')
|
|
})
|
|
|
|
resolveRetry?.({ result: 'success' })
|
|
await waitFor(() => {
|
|
expect(mockRefetch).toHaveBeenCalled()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('State Management', () => {
|
|
it('should transition to error state when retry fails', async () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({
|
|
total: 1,
|
|
data: [{ id: 'doc1' }] as ErrorDocsResponse['data'],
|
|
}, false),
|
|
)
|
|
|
|
mockRetryErrorDocs.mockResolvedValue({ result: 'fail' })
|
|
|
|
render(<RetryButton datasetId="test-dataset" />)
|
|
|
|
const retryButton = screen.getByText(/retry/i)
|
|
fireEvent.click(retryButton)
|
|
|
|
// Wait for retry to complete and state to update
|
|
await waitFor(() => {
|
|
expect(mockRetryErrorDocs).toHaveBeenCalled()
|
|
})
|
|
|
|
// Button should still be visible after failed retry
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/retry/i)).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('should transition to success state when total becomes 0', async () => {
|
|
const { rerender } = render(<RetryButton datasetId="test-dataset" />)
|
|
|
|
// Initially has errors
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({
|
|
total: 1,
|
|
data: [{ id: 'doc1' }] as ErrorDocsResponse['data'],
|
|
}, false),
|
|
)
|
|
|
|
rerender(<RetryButton datasetId="test-dataset" />)
|
|
expect(screen.getByText(/retry/i)).toBeInTheDocument()
|
|
|
|
// Now no errors
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({ total: 0, data: [] }, false),
|
|
)
|
|
|
|
rerender(<RetryButton datasetId="test-dataset" />)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.queryByText(/retry/i)).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should handle empty data array', () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({ total: 0, data: [] }, false),
|
|
)
|
|
|
|
const { container } = render(<RetryButton datasetId="test-dataset" />)
|
|
expect(container.firstChild).toBeNull()
|
|
})
|
|
|
|
it('should handle undefined data by showing error state', () => {
|
|
// When data is undefined but not loading, the component shows error state
|
|
// because errorDocs?.total is not strictly equal to 0
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult(undefined, false),
|
|
)
|
|
|
|
render(<RetryButton datasetId="test-dataset" />)
|
|
// Component renders with undefined count
|
|
expect(screen.getByText(/retry/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle retry with empty document list', async () => {
|
|
mockUseDatasetErrorDocs.mockReturnValue(
|
|
createMockQueryResult({ total: 1, data: [] }, false),
|
|
)
|
|
|
|
mockRetryErrorDocs.mockResolvedValue({ result: 'success' })
|
|
|
|
render(<RetryButton datasetId="test-dataset" />)
|
|
|
|
const retryButton = screen.getByText(/retry/i)
|
|
fireEvent.click(retryButton)
|
|
|
|
await waitFor(() => {
|
|
expect(mockRetryErrorDocs).toHaveBeenCalledWith({
|
|
datasetId: 'test-dataset',
|
|
document_ids: [],
|
|
})
|
|
})
|
|
|
|
// Wait for all state updates to complete
|
|
await waitFor(() => {
|
|
expect(mockRefetch).toHaveBeenCalled()
|
|
})
|
|
})
|
|
})
|
|
})
|