refactor(web): migrate to Vitest and esm (#29974)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
Stephen Zhou
2025-12-22 16:35:22 +08:00
committed by GitHub
parent 42f7ecda12
commit eabdc5f0eb
268 changed files with 5455 additions and 6307 deletions

View File

@ -1,3 +1,4 @@
import type { MockedFunction } from 'vitest'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import React from 'react'
import EmptyDatasetCreationModal from './index'
@ -5,47 +6,47 @@ import { createEmptyDataset } from '@/service/datasets'
import { useInvalidDatasetList } from '@/service/knowledge/use-dataset'
// Mock Next.js router
const mockPush = jest.fn()
jest.mock('next/navigation', () => ({
const mockPush = vi.fn()
vi.mock('next/navigation', () => ({
useRouter: () => ({
push: mockPush,
}),
}))
// Mock createEmptyDataset API
jest.mock('@/service/datasets', () => ({
createEmptyDataset: jest.fn(),
vi.mock('@/service/datasets', () => ({
createEmptyDataset: vi.fn(),
}))
// Mock useInvalidDatasetList hook
jest.mock('@/service/knowledge/use-dataset', () => ({
useInvalidDatasetList: jest.fn(),
vi.mock('@/service/knowledge/use-dataset', () => ({
useInvalidDatasetList: vi.fn(),
}))
// Mock ToastContext - need to mock both createContext and useContext from use-context-selector
const mockNotify = jest.fn()
jest.mock('use-context-selector', () => ({
createContext: jest.fn(() => ({
const mockNotify = vi.fn()
vi.mock('use-context-selector', () => ({
createContext: vi.fn(() => ({
Provider: ({ children }: { children: React.ReactNode }) => children,
})),
useContext: jest.fn(() => ({ notify: mockNotify })),
useContext: vi.fn(() => ({ notify: mockNotify })),
}))
// Type cast mocked functions
const mockCreateEmptyDataset = createEmptyDataset as jest.MockedFunction<typeof createEmptyDataset>
const mockInvalidDatasetList = jest.fn()
const mockUseInvalidDatasetList = useInvalidDatasetList as jest.MockedFunction<typeof useInvalidDatasetList>
const mockCreateEmptyDataset = createEmptyDataset as MockedFunction<typeof createEmptyDataset>
const mockInvalidDatasetList = vi.fn()
const mockUseInvalidDatasetList = useInvalidDatasetList as MockedFunction<typeof useInvalidDatasetList>
// Test data builder for props
const createDefaultProps = (overrides?: Partial<{ show: boolean; onHide: () => void }>) => ({
show: true,
onHide: jest.fn(),
onHide: vi.fn(),
...overrides,
})
describe('EmptyDatasetCreationModal', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
mockUseInvalidDatasetList.mockReturnValue(mockInvalidDatasetList)
mockCreateEmptyDataset.mockResolvedValue({
id: 'dataset-123',
@ -115,7 +116,7 @@ describe('EmptyDatasetCreationModal', () => {
describe('show prop', () => {
it('should show modal when show is true', () => {
// Arrange & Act
render(<EmptyDatasetCreationModal show={true} onHide={jest.fn()} />)
render(<EmptyDatasetCreationModal show={true} onHide={vi.fn()} />)
// Assert
expect(screen.getByText('datasetCreation.stepOne.modal.title')).toBeInTheDocument()
@ -123,7 +124,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should hide modal when show is false', () => {
// Arrange & Act
render(<EmptyDatasetCreationModal show={false} onHide={jest.fn()} />)
render(<EmptyDatasetCreationModal show={false} onHide={vi.fn()} />)
// Assert
expect(screen.queryByText('datasetCreation.stepOne.modal.title')).not.toBeInTheDocument()
@ -131,7 +132,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should toggle visibility when show prop changes', () => {
// Arrange
const onHide = jest.fn()
const onHide = vi.fn()
const { rerender } = render(<EmptyDatasetCreationModal show={false} onHide={onHide} />)
// Act & Assert - Initially hidden
@ -146,7 +147,7 @@ describe('EmptyDatasetCreationModal', () => {
describe('onHide prop', () => {
it('should call onHide when cancel button is clicked', () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
// Act
@ -159,7 +160,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should call onHide when close icon is clicked', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
// Act - Wait for modal to be rendered, then find the close span
@ -196,7 +197,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should persist input value when modal is hidden and shown again via rerender', () => {
// Arrange
const onHide = jest.fn()
const onHide = vi.fn()
const { rerender } = render(<EmptyDatasetCreationModal show={true} onHide={onHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder') as HTMLInputElement
@ -237,7 +238,7 @@ describe('EmptyDatasetCreationModal', () => {
describe('User Interactions', () => {
it('should submit form when confirm button is clicked with valid input', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -295,7 +296,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should allow exactly 40 characters', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -313,7 +314,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should close modal on cancel button click', () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const cancelButton = screen.getByText('datasetCreation.stepOne.modal.cancelButton')
@ -331,7 +332,7 @@ describe('EmptyDatasetCreationModal', () => {
describe('API Calls', () => {
it('should call createEmptyDataset with correct parameters', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -348,7 +349,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should call invalidDatasetList after successful creation', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -365,7 +366,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should call onHide after successful creation', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -383,7 +384,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should show error notification on API failure', async () => {
// Arrange
mockCreateEmptyDataset.mockRejectedValue(new Error('API Error'))
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -404,7 +405,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should not call onHide on API failure', async () => {
// Arrange
mockCreateEmptyDataset.mockRejectedValue(new Error('API Error'))
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -451,7 +452,7 @@ describe('EmptyDatasetCreationModal', () => {
id: 'test-dataset-456',
name: 'Test',
} as ReturnType<typeof createEmptyDataset> extends Promise<infer T> ? T : never)
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -508,7 +509,7 @@ describe('EmptyDatasetCreationModal', () => {
describe('Edge Cases', () => {
it('should handle whitespace-only input as valid (component behavior)', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -525,7 +526,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should handle special characters in input', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -542,7 +543,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should handle Unicode characters in input', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -559,7 +560,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should handle input at exactly 40 character boundary', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -599,7 +600,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should handle rapid consecutive submits', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -618,7 +619,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should handle input with leading/trailing spaces', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -635,7 +636,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should handle newline characters in input (browser strips newlines)', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')
const confirmButton = screen.getByText('datasetCreation.stepOne.modal.confirmButton')
@ -719,7 +720,7 @@ describe('EmptyDatasetCreationModal', () => {
describe('Integration', () => {
it('should complete full successful creation flow', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
mockCreateEmptyDataset.mockResolvedValue({
id: 'new-id-789',
name: 'Complete Flow Test',
@ -747,7 +748,7 @@ describe('EmptyDatasetCreationModal', () => {
it('should handle error flow correctly', async () => {
// Arrange
const mockOnHide = jest.fn()
const mockOnHide = vi.fn()
mockCreateEmptyDataset.mockRejectedValue(new Error('Server Error'))
render(<EmptyDatasetCreationModal show={true} onHide={mockOnHide} />)
const input = screen.getByPlaceholderText('datasetCreation.stepOne.modal.placeholder')

View File

@ -1,35 +1,40 @@
import type { MockedFunction } from 'vitest'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import FilePreview from './index'
import type { CustomFile as File } from '@/models/datasets'
import { fetchFilePreview } from '@/service/common'
// Mock the fetchFilePreview service
jest.mock('@/service/common', () => ({
fetchFilePreview: jest.fn(),
vi.mock('@/service/common', () => ({
fetchFilePreview: vi.fn(),
}))
const mockFetchFilePreview = fetchFilePreview as jest.MockedFunction<typeof fetchFilePreview>
const mockFetchFilePreview = fetchFilePreview as MockedFunction<typeof fetchFilePreview>
// Factory function to create mock file objects
const createMockFile = (overrides: Partial<File> = {}): File => {
const file = new window.File(['test content'], 'test-file.txt', {
const fileName = overrides.name ?? 'test-file.txt'
// Create a plain object that looks like a File with CustomFile properties
// We can't use Object.assign on a real File because 'name' is a getter-only property
return {
name: fileName,
size: 1024,
type: 'text/plain',
}) as File
return Object.assign(file, {
lastModified: Date.now(),
id: 'file-123',
extension: 'txt',
mime_type: 'text/plain',
created_by: 'user-1',
created_at: Date.now(),
...overrides,
})
} as File
}
// Helper to render FilePreview with default props
const renderFilePreview = (props: Partial<{ file?: File; hidePreview: () => void }> = {}) => {
const defaultProps = {
file: createMockFile(),
hidePreview: jest.fn(),
hidePreview: vi.fn(),
...props,
}
return {
@ -48,7 +53,7 @@ const findLoadingSpinner = (container: HTMLElement) => {
// ============================================================================
describe('FilePreview', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
// Default successful API response
mockFetchFilePreview.mockResolvedValue({ content: 'Preview content here' })
})
@ -168,7 +173,7 @@ describe('FilePreview', () => {
// Act - Initial render
const { rerender, container } = render(
<FilePreview file={file1} hidePreview={jest.fn()} />,
<FilePreview file={file1} hidePreview={vi.fn()} />,
)
// First file loading - spinner should be visible
@ -184,7 +189,7 @@ describe('FilePreview', () => {
})
// Rerender with new file
rerender(<FilePreview file={file2} hidePreview={jest.fn()} />)
rerender(<FilePreview file={file2} hidePreview={vi.fn()} />)
// Should show loading again
await waitFor(() => {
@ -245,14 +250,14 @@ describe('FilePreview', () => {
// Act
const { rerender } = render(
<FilePreview file={file1} hidePreview={jest.fn()} />,
<FilePreview file={file1} hidePreview={vi.fn()} />,
)
await waitFor(() => {
expect(mockFetchFilePreview).toHaveBeenCalledWith({ fileID: 'file-1' })
})
rerender(<FilePreview file={file2} hidePreview={jest.fn()} />)
rerender(<FilePreview file={file2} hidePreview={vi.fn()} />)
// Assert
await waitFor(() => {
@ -310,7 +315,7 @@ describe('FilePreview', () => {
describe('User Interactions', () => {
it('should call hidePreview when close button is clicked', async () => {
// Arrange
const hidePreview = jest.fn()
const hidePreview = vi.fn()
const { container } = renderFilePreview({ hidePreview })
// Act
@ -323,7 +328,7 @@ describe('FilePreview', () => {
it('should call hidePreview with event object when clicked', async () => {
// Arrange
const hidePreview = jest.fn()
const hidePreview = vi.fn()
const { container } = renderFilePreview({ hidePreview })
// Act
@ -337,7 +342,7 @@ describe('FilePreview', () => {
it('should handle multiple clicks on close button', async () => {
// Arrange
const hidePreview = jest.fn()
const hidePreview = vi.fn()
const { container } = renderFilePreview({ hidePreview })
// Act
@ -391,7 +396,7 @@ describe('FilePreview', () => {
// Act
const { rerender, container } = render(
<FilePreview file={file1} hidePreview={jest.fn()} />,
<FilePreview file={file1} hidePreview={vi.fn()} />,
)
await waitFor(() => {
@ -399,7 +404,7 @@ describe('FilePreview', () => {
})
// Change file
rerender(<FilePreview file={file2} hidePreview={jest.fn()} />)
rerender(<FilePreview file={file2} hidePreview={vi.fn()} />)
// Assert - Loading should be shown again
await waitFor(() => {
@ -421,7 +426,7 @@ describe('FilePreview', () => {
// Act
const { rerender } = render(
<FilePreview file={file1} hidePreview={jest.fn()} />,
<FilePreview file={file1} hidePreview={vi.fn()} />,
)
await waitFor(() => {
@ -429,7 +434,7 @@ describe('FilePreview', () => {
})
// Change file - loading should replace content
rerender(<FilePreview file={file2} hidePreview={jest.fn()} />)
rerender(<FilePreview file={file2} hidePreview={vi.fn()} />)
// Resolve second fetch
await act(async () => {
@ -487,7 +492,7 @@ describe('FilePreview', () => {
const { container } = renderFilePreview({ file })
// Assert - getFileName returns empty for single segment, but component still renders
const fileNameElement = container.querySelector('.fileName')
const fileNameElement = container.querySelector('[class*="fileName"]')
expect(fileNameElement).toBeInTheDocument()
// The first span (file name) should be empty
const fileNameSpan = fileNameElement?.querySelector('span:first-child')
@ -509,7 +514,7 @@ describe('FilePreview', () => {
describe('hidePreview prop', () => {
it('should accept hidePreview callback', async () => {
// Arrange
const hidePreview = jest.fn()
const hidePreview = vi.fn()
// Act
renderFilePreview({ hidePreview })
@ -594,7 +599,7 @@ describe('FilePreview', () => {
// Assert - Should render as text, not execute scripts
await waitFor(() => {
const contentDiv = container.querySelector('.fileContent')
const contentDiv = container.querySelector('[class*="fileContent"]')
expect(contentDiv).toBeInTheDocument()
// Content is escaped by React, so HTML entities are displayed
expect(contentDiv?.textContent).toContain('alert')
@ -625,7 +630,7 @@ describe('FilePreview', () => {
// Assert - Content should be in the DOM
await waitFor(() => {
const contentDiv = container.querySelector('.fileContent')
const contentDiv = container.querySelector('[class*="fileContent"]')
expect(contentDiv).toBeInTheDocument()
expect(contentDiv?.textContent).toContain('Line 1')
expect(contentDiv?.textContent).toContain('Line 2')
@ -658,14 +663,14 @@ describe('FilePreview', () => {
// Act
const { rerender } = render(
<FilePreview file={file1} hidePreview={jest.fn()} />,
<FilePreview file={file1} hidePreview={vi.fn()} />,
)
await waitFor(() => {
expect(mockFetchFilePreview).toHaveBeenCalledTimes(1)
})
rerender(<FilePreview file={file2} hidePreview={jest.fn()} />)
rerender(<FilePreview file={file2} hidePreview={vi.fn()} />)
// Assert
await waitFor(() => {
@ -676,8 +681,8 @@ describe('FilePreview', () => {
it('should not trigger effect when hidePreview changes', async () => {
// Arrange
const file = createMockFile()
const hidePreview1 = jest.fn()
const hidePreview2 = jest.fn()
const hidePreview1 = vi.fn()
const hidePreview2 = vi.fn()
// Act
const { rerender } = render(
@ -705,12 +710,12 @@ describe('FilePreview', () => {
// Act
const { rerender } = render(
<FilePreview file={files[0]} hidePreview={jest.fn()} />,
<FilePreview file={files[0]} hidePreview={vi.fn()} />,
)
// Rapidly change files
for (let i = 1; i < files.length; i++)
rerender(<FilePreview file={files[i]} hidePreview={jest.fn()} />)
rerender(<FilePreview file={files[i]} hidePreview={vi.fn()} />)
// Assert - Should have called API for each file
await waitFor(() => {
@ -740,14 +745,14 @@ describe('FilePreview', () => {
// Act
const { rerender, container } = render(
<FilePreview file={file} hidePreview={jest.fn()} />,
<FilePreview file={file} hidePreview={vi.fn()} />,
)
await waitFor(() => {
expect(mockFetchFilePreview).toHaveBeenCalledTimes(1)
})
rerender(<FilePreview file={undefined} hidePreview={jest.fn()} />)
rerender(<FilePreview file={undefined} hidePreview={vi.fn()} />)
// Assert - Should not crash, API should not be called again
expect(container.firstChild).toBeInTheDocument()
@ -789,7 +794,7 @@ describe('FilePreview', () => {
const { container } = renderFilePreview({ file })
// Assert - slice(0, -1) on single element array returns empty
const fileNameElement = container.querySelector('.fileName')
const fileNameElement = container.querySelector('[class*="fileName"]')
const firstSpan = fileNameElement?.querySelector('span:first-child')
expect(firstSpan?.textContent).toBe('')
})

View File

@ -18,23 +18,23 @@ const IndexingTypeValues = {
// Mock External Dependencies
// ==========================================
// Mock react-i18next (handled by __mocks__/react-i18next.ts but we override for custom messages)
jest.mock('react-i18next', () => ({
// Mock react-i18next (handled by global mock in web/vitest.setup.ts but we override for custom messages)
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
// Mock next/link
jest.mock('next/link', () => {
vi.mock('next/link', () => {
return function MockLink({ children, href }: { children: React.ReactNode; href: string }) {
return <a href={href}>{children}</a>
}
})
// Mock modal context
const mockSetShowAccountSettingModal = jest.fn()
jest.mock('@/context/modal-context', () => ({
const mockSetShowAccountSettingModal = vi.fn()
vi.mock('@/context/modal-context', () => ({
useModalContextSelector: (selector: (state: any) => any) => {
const state = {
setShowAccountSettingModal: mockSetShowAccountSettingModal,
@ -45,7 +45,7 @@ jest.mock('@/context/modal-context', () => ({
// Mock dataset detail context
let mockDatasetDetail: DataSet | undefined
jest.mock('@/context/dataset-detail', () => ({
vi.mock('@/context/dataset-detail', () => ({
useDatasetDetailContextWithSelector: (selector: (state: any) => any) => {
const state = {
dataset: mockDatasetDetail,
@ -56,10 +56,10 @@ jest.mock('@/context/dataset-detail', () => ({
// Mock useDefaultModel hook
let mockEmbeddingsDefaultModel: { model: string; provider: string } | undefined
jest.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', () => ({
useDefaultModel: () => ({
data: mockEmbeddingsDefaultModel,
mutate: jest.fn(),
mutate: vi.fn(),
isLoading: false,
}),
}))
@ -68,7 +68,7 @@ jest.mock('@/app/components/header/account-setting/model-provider-page/hooks', (
let mockDataSourceList: { result: DataSourceAuth[] } | undefined
let mockIsLoadingDataSourceList = false
let mockFetchingError = false
jest.mock('@/service/use-datasource', () => ({
vi.mock('@/service/use-datasource', () => ({
useGetDefaultDataSourceListAuth: () => ({
data: mockDataSourceList,
isLoading: mockIsLoadingDataSourceList,
@ -87,7 +87,7 @@ let stepThreeProps: Record<string, any> = {}
// _topBarProps is assigned but not directly used in assertions - values checked via data-testid
let _topBarProps: Record<string, any> = {}
jest.mock('./step-one', () => ({
vi.mock('./step-one', () => ({
__esModule: true,
default: (props: Record<string, any>) => {
stepOneProps = props
@ -161,7 +161,7 @@ jest.mock('./step-one', () => ({
},
}))
jest.mock('./step-two', () => ({
vi.mock('./step-two', () => ({
__esModule: true,
default: (props: Record<string, any>) => {
stepTwoProps = props
@ -196,7 +196,7 @@ jest.mock('./step-two', () => ({
},
}))
jest.mock('./step-three', () => ({
vi.mock('./step-three', () => ({
__esModule: true,
default: (props: Record<string, any>) => {
stepThreeProps = props
@ -211,7 +211,7 @@ jest.mock('./step-three', () => ({
},
}))
jest.mock('./top-bar', () => ({
vi.mock('./top-bar', () => ({
TopBar: (props: Record<string, any>) => {
_topBarProps = props
return (
@ -300,7 +300,7 @@ const createMockDataSourceAuth = (overrides?: Partial<DataSourceAuth>): DataSour
describe('DatasetUpdateForm', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
// Reset mock state
mockDatasetDetail = undefined
mockEmbeddingsDefaultModel = { model: 'text-embedding-ada-002', provider: 'openai' }

View File

@ -1,14 +1,15 @@
import type { MockedFunction } from 'vitest'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import NotionPagePreview from './index'
import type { NotionPage } from '@/models/common'
import { fetchNotionPagePreview } from '@/service/datasets'
// Mock the fetchNotionPagePreview service
jest.mock('@/service/datasets', () => ({
fetchNotionPagePreview: jest.fn(),
vi.mock('@/service/datasets', () => ({
fetchNotionPagePreview: vi.fn(),
}))
const mockFetchNotionPagePreview = fetchNotionPagePreview as jest.MockedFunction<typeof fetchNotionPagePreview>
const mockFetchNotionPagePreview = fetchNotionPagePreview as MockedFunction<typeof fetchNotionPagePreview>
// Factory function to create mock NotionPage objects
const createMockNotionPage = (overrides: Partial<NotionPage> = {}): NotionPage => {
@ -60,7 +61,7 @@ const renderNotionPagePreview = async (
const defaultProps = {
currentPage: createMockNotionPage(),
notionCredentialId: 'credential-123',
hidePreview: jest.fn(),
hidePreview: vi.fn(),
...props,
}
const result = render(<NotionPagePreview {...defaultProps} />)
@ -93,7 +94,7 @@ const findLoadingSpinner = (container: HTMLElement) => {
// ============================================================================
describe('NotionPagePreview', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
// Default successful API response
mockFetchNotionPagePreview.mockResolvedValue({ content: 'Preview content here' })
})
@ -256,7 +257,7 @@ describe('NotionPagePreview', () => {
// Act - Initial render
const { rerender, container } = render(
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={jest.fn()} />,
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={vi.fn()} />,
)
// First page loading - spinner should be visible
@ -272,7 +273,7 @@ describe('NotionPagePreview', () => {
})
// Rerender with new page
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={jest.fn()} />)
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={vi.fn()} />)
// Should show loading again
await waitFor(() => {
@ -330,7 +331,7 @@ describe('NotionPagePreview', () => {
// Act
const { rerender } = render(
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={jest.fn()} />,
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={vi.fn()} />,
)
await waitFor(() => {
@ -342,7 +343,7 @@ describe('NotionPagePreview', () => {
})
await act(async () => {
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={jest.fn()} />)
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={vi.fn()} />)
})
// Assert
@ -401,7 +402,7 @@ describe('NotionPagePreview', () => {
describe('User Interactions', () => {
it('should call hidePreview when close button is clicked', async () => {
// Arrange
const hidePreview = jest.fn()
const hidePreview = vi.fn()
const { container } = await renderNotionPagePreview({ hidePreview })
// Act
@ -414,7 +415,7 @@ describe('NotionPagePreview', () => {
it('should handle multiple clicks on close button', async () => {
// Arrange
const hidePreview = jest.fn()
const hidePreview = vi.fn()
const { container } = await renderNotionPagePreview({ hidePreview })
// Act
@ -466,7 +467,7 @@ describe('NotionPagePreview', () => {
// Act
const { rerender, container } = render(
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={jest.fn()} />,
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={vi.fn()} />,
)
await waitFor(() => {
@ -475,7 +476,7 @@ describe('NotionPagePreview', () => {
// Change page
await act(async () => {
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={jest.fn()} />)
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={vi.fn()} />)
})
// Assert - Loading should be shown again
@ -498,7 +499,7 @@ describe('NotionPagePreview', () => {
// Act
const { rerender } = render(
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={jest.fn()} />,
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={vi.fn()} />,
)
await waitFor(() => {
@ -507,7 +508,7 @@ describe('NotionPagePreview', () => {
// Change page
await act(async () => {
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={jest.fn()} />)
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={vi.fn()} />)
})
// Resolve second fetch
@ -613,7 +614,7 @@ describe('NotionPagePreview', () => {
describe('hidePreview prop', () => {
it('should accept hidePreview callback', async () => {
// Arrange
const hidePreview = jest.fn()
const hidePreview = vi.fn()
// Act
await renderNotionPagePreview({ hidePreview })
@ -673,7 +674,7 @@ describe('NotionPagePreview', () => {
const { container } = await renderNotionPagePreview()
// Assert - Should render as text, not execute scripts
const contentDiv = container.querySelector('.fileContent')
const contentDiv = container.querySelector('[class*="fileContent"]')
expect(contentDiv).toBeInTheDocument()
expect(contentDiv?.textContent).toContain('alert')
})
@ -699,7 +700,7 @@ describe('NotionPagePreview', () => {
const { container } = await renderNotionPagePreview()
// Assert
const contentDiv = container.querySelector('.fileContent')
const contentDiv = container.querySelector('[class*="fileContent"]')
expect(contentDiv).toBeInTheDocument()
expect(contentDiv?.textContent).toContain('Line 1')
expect(contentDiv?.textContent).toContain('Line 2')
@ -742,7 +743,7 @@ describe('NotionPagePreview', () => {
// Act
const { rerender } = render(
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={jest.fn()} />,
<NotionPagePreview currentPage={page1} notionCredentialId="cred-123" hidePreview={vi.fn()} />,
)
await waitFor(() => {
@ -750,7 +751,7 @@ describe('NotionPagePreview', () => {
})
await act(async () => {
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={jest.fn()} />)
rerender(<NotionPagePreview currentPage={page2} notionCredentialId="cred-123" hidePreview={vi.fn()} />)
})
// Assert
@ -762,8 +763,8 @@ describe('NotionPagePreview', () => {
it('should not trigger effect when hidePreview changes', async () => {
// Arrange
const page = createMockNotionPage()
const hidePreview1 = jest.fn()
const hidePreview2 = jest.fn()
const hidePreview1 = vi.fn()
const hidePreview2 = vi.fn()
// Act
const { rerender } = render(
@ -789,7 +790,7 @@ describe('NotionPagePreview', () => {
// Act
const { rerender } = render(
<NotionPagePreview currentPage={page} notionCredentialId="cred-1" hidePreview={jest.fn()} />,
<NotionPagePreview currentPage={page} notionCredentialId="cred-1" hidePreview={vi.fn()} />,
)
await waitFor(() => {
@ -797,7 +798,7 @@ describe('NotionPagePreview', () => {
})
await act(async () => {
rerender(<NotionPagePreview currentPage={page} notionCredentialId="cred-2" hidePreview={jest.fn()} />)
rerender(<NotionPagePreview currentPage={page} notionCredentialId="cred-2" hidePreview={vi.fn()} />)
})
// Assert - Should not call API again (only currentPage is in dependency array)
@ -812,13 +813,13 @@ describe('NotionPagePreview', () => {
// Act
const { rerender } = render(
<NotionPagePreview currentPage={pages[0]} notionCredentialId="cred-123" hidePreview={jest.fn()} />,
<NotionPagePreview currentPage={pages[0]} notionCredentialId="cred-123" hidePreview={vi.fn()} />,
)
// Rapidly change pages
for (let i = 1; i < pages.length; i++) {
await act(async () => {
rerender(<NotionPagePreview currentPage={pages[i]} notionCredentialId="cred-123" hidePreview={jest.fn()} />)
rerender(<NotionPagePreview currentPage={pages[i]} notionCredentialId="cred-123" hidePreview={vi.fn()} />)
})
}
@ -850,7 +851,7 @@ describe('NotionPagePreview', () => {
// Act
const { rerender, container } = render(
<NotionPagePreview currentPage={page} notionCredentialId="cred-123" hidePreview={jest.fn()} />,
<NotionPagePreview currentPage={page} notionCredentialId="cred-123" hidePreview={vi.fn()} />,
)
await waitFor(() => {
@ -858,7 +859,7 @@ describe('NotionPagePreview', () => {
})
await act(async () => {
rerender(<NotionPagePreview currentPage={undefined} notionCredentialId="cred-123" hidePreview={jest.fn()} />)
rerender(<NotionPagePreview currentPage={undefined} notionCredentialId="cred-123" hidePreview={vi.fn()} />)
})
// Assert - Should not crash, API should not be called again
@ -1075,7 +1076,7 @@ describe('NotionPagePreview', () => {
it('should handle page with icon object having empty url', async () => {
// Arrange
// Suppress console.error for this test as we're intentionally testing empty src edge case
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn())
const page = createMockNotionPage({
page_icon: {
@ -1112,7 +1113,7 @@ describe('NotionPagePreview', () => {
const { container } = await renderNotionPagePreview()
// Assert
const contentDiv = container.querySelector('.fileContent')
const contentDiv = container.querySelector('[class*="fileContent"]')
expect(contentDiv).toBeInTheDocument()
expect(contentDiv).toHaveTextContent('Test content')
})
@ -1126,7 +1127,7 @@ describe('NotionPagePreview', () => {
const { container } = await renderNotionPagePreview()
// Assert
const contentDiv = container.querySelector('.fileContent')
const contentDiv = container.querySelector('[class*="fileContent"]')
expect(contentDiv).toBeInTheDocument()
// The CSS class has white-space: pre-line
expect(contentDiv?.textContent).toContain('indented content')
@ -1142,7 +1143,7 @@ describe('NotionPagePreview', () => {
// Assert
const loadingElement = findLoadingSpinner(container)
expect(loadingElement).not.toBeInTheDocument()
const contentDiv = container.querySelector('.fileContent')
const contentDiv = container.querySelector('[class*="fileContent"]')
expect(contentDiv).toBeInTheDocument()
expect(contentDiv?.textContent).toBe('')
})

View File

@ -3,9 +3,9 @@ import StepThree from './index'
import type { FullDocumentDetail, IconInfo, createDocumentResponse } from '@/models/datasets'
// Mock the EmbeddingProcess component since it has complex async logic
jest.mock('../embedding-process', () => ({
vi.mock('../embedding-process', () => ({
__esModule: true,
default: jest.fn(({ datasetId, batchId, documents, indexingType, retrievalMethod }) => (
default: vi.fn(({ datasetId, batchId, documents, indexingType, retrievalMethod }) => (
<div data-testid="embedding-process">
<span data-testid="ep-dataset-id">{datasetId}</span>
<span data-testid="ep-batch-id">{batchId}</span>
@ -18,18 +18,18 @@ jest.mock('../embedding-process', () => ({
// Mock useBreakpoints hook
let mockMediaType = 'pc'
jest.mock('@/hooks/use-breakpoints', () => ({
vi.mock('@/hooks/use-breakpoints', () => ({
__esModule: true,
MediaType: {
mobile: 'mobile',
tablet: 'tablet',
pc: 'pc',
},
default: jest.fn(() => mockMediaType),
default: vi.fn(() => mockMediaType),
}))
// Mock useDocLink hook
jest.mock('@/context/i18n', () => ({
vi.mock('@/context/i18n', () => ({
useDocLink: () => (path?: string) => `https://docs.dify.ai/en-US${path || ''}`,
}))
@ -104,7 +104,7 @@ const renderStepThree = (props: Partial<Parameters<typeof StepThree>[0]> = {}) =
// ============================================================================
describe('StepThree', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
mockMediaType = 'pc'
})

View File

@ -10,14 +10,14 @@ const supportedLanguages = languages.filter(lang => lang.supported)
// Test data builder for props
const createDefaultProps = (overrides?: Partial<ILanguageSelectProps>): ILanguageSelectProps => ({
currentLanguage: 'English',
onSelect: jest.fn(),
onSelect: vi.fn(),
disabled: false,
...overrides,
})
describe('LanguageSelect', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
// ==========================================
@ -189,7 +189,7 @@ describe('LanguageSelect', () => {
describe('onSelect prop', () => {
it('should be callable as a function', () => {
const mockOnSelect = jest.fn()
const mockOnSelect = vi.fn()
const props = createDefaultProps({ onSelect: mockOnSelect })
render(<LanguageSelect {...props} />)
@ -224,7 +224,7 @@ describe('LanguageSelect', () => {
it('should call onSelect when a language option is clicked', () => {
// Arrange
const mockOnSelect = jest.fn()
const mockOnSelect = vi.fn()
const props = createDefaultProps({ onSelect: mockOnSelect })
render(<LanguageSelect {...props} />)
@ -241,7 +241,7 @@ describe('LanguageSelect', () => {
it('should call onSelect with correct language when selecting different languages', () => {
// Arrange
const mockOnSelect = jest.fn()
const mockOnSelect = vi.fn()
const props = createDefaultProps({ onSelect: mockOnSelect })
render(<LanguageSelect {...props} />)
@ -274,7 +274,7 @@ describe('LanguageSelect', () => {
it('should not call onSelect when component is disabled', () => {
// Arrange
const mockOnSelect = jest.fn()
const mockOnSelect = vi.fn()
const props = createDefaultProps({ onSelect: mockOnSelect, disabled: true })
render(<LanguageSelect {...props} />)
@ -288,7 +288,7 @@ describe('LanguageSelect', () => {
it('should handle rapid consecutive clicks', () => {
// Arrange
const mockOnSelect = jest.fn()
const mockOnSelect = vi.fn()
const props = createDefaultProps({ onSelect: mockOnSelect })
render(<LanguageSelect {...props} />)
@ -314,9 +314,9 @@ describe('LanguageSelect', () => {
it('should not re-render when props remain the same', () => {
// Arrange
const mockOnSelect = jest.fn()
const mockOnSelect = vi.fn()
const props = createDefaultProps({ onSelect: mockOnSelect })
const renderSpy = jest.fn()
const renderSpy = vi.fn()
// Create a wrapper component to track renders
const TrackedLanguageSelect: React.FC<ILanguageSelectProps> = (trackedProps) => {
@ -515,7 +515,7 @@ describe('LanguageSelect', () => {
describe('Popover Integration', () => {
it('should use manualClose prop on Popover', () => {
// Arrange
const mockOnSelect = jest.fn()
const mockOnSelect = vi.fn()
const props = createDefaultProps({ onSelect: mockOnSelect })
// Act

View File

@ -23,7 +23,7 @@ const createQAProps = (overrides?: Partial<IPreviewItemProps>): IPreviewItemProp
describe('PreviewItem', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
// ==========================================
@ -346,7 +346,7 @@ describe('PreviewItem', () => {
it('should not re-render when props remain the same', () => {
// Arrange
const props = createDefaultProps()
const renderSpy = jest.fn()
const renderSpy = vi.fn()
// Create a wrapper component to track renders
const TrackedPreviewItem: React.FC<IPreviewItemProps> = (trackedProps) => {

View File

@ -37,7 +37,7 @@ const renderStepperStep = (props: Partial<StepperStepProps> = {}) => {
// ============================================================================
describe('Stepper', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
// --------------------------------------------------------------------------
@ -332,7 +332,7 @@ describe('Stepper', () => {
// ============================================================================
describe('StepperStep', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
// --------------------------------------------------------------------------
@ -671,7 +671,7 @@ describe('StepperStep', () => {
// ============================================================================
describe('Stepper Integration', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
it('should pass correct props to each StepperStep', () => {

View File

@ -1,3 +1,4 @@
import type { MockInstance } from 'vitest'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import StopEmbeddingModal from './index'
@ -12,8 +13,8 @@ type StopEmbeddingModalProps = {
const renderStopEmbeddingModal = (props: Partial<StopEmbeddingModalProps> = {}) => {
const defaultProps: StopEmbeddingModalProps = {
show: true,
onConfirm: jest.fn(),
onHide: jest.fn(),
onConfirm: vi.fn(),
onHide: vi.fn(),
...props,
}
return {
@ -28,12 +29,12 @@ const renderStopEmbeddingModal = (props: Partial<StopEmbeddingModalProps> = {})
describe('StopEmbeddingModal', () => {
// Suppress Headless UI warnings in tests
// These warnings are from the library's internal behavior, not our code
let consoleWarnSpy: jest.SpyInstance
let consoleErrorSpy: jest.SpyInstance
let consoleWarnSpy: MockInstance
let consoleErrorSpy: MockInstance
beforeAll(() => {
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(jest.fn())
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(vi.fn())
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn())
})
afterAll(() => {
@ -42,7 +43,7 @@ describe('StopEmbeddingModal', () => {
})
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
// --------------------------------------------------------------------------
@ -159,8 +160,8 @@ describe('StopEmbeddingModal', () => {
it('should use default value false when show is not provided', () => {
// Arrange & Act
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
render(<StopEmbeddingModal onConfirm={onConfirm} onHide={onHide} show={false} />)
// Assert
@ -169,8 +170,8 @@ describe('StopEmbeddingModal', () => {
it('should toggle visibility when show prop changes to true', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
// Act - Initially hidden
const { rerender } = render(
@ -193,7 +194,7 @@ describe('StopEmbeddingModal', () => {
describe('onConfirm prop', () => {
it('should accept onConfirm callback function', () => {
// Arrange
const onConfirm = jest.fn()
const onConfirm = vi.fn()
// Act
renderStopEmbeddingModal({ onConfirm })
@ -206,7 +207,7 @@ describe('StopEmbeddingModal', () => {
describe('onHide prop', () => {
it('should accept onHide callback function', () => {
// Arrange
const onHide = jest.fn()
const onHide = vi.fn()
// Act
renderStopEmbeddingModal({ onHide })
@ -224,8 +225,8 @@ describe('StopEmbeddingModal', () => {
describe('Confirm Button', () => {
it('should call onConfirm when confirm button is clicked', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -240,8 +241,8 @@ describe('StopEmbeddingModal', () => {
it('should call onHide when confirm button is clicked', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -257,8 +258,8 @@ describe('StopEmbeddingModal', () => {
it('should call both onConfirm and onHide in correct order when confirm button is clicked', async () => {
// Arrange
const callOrder: string[] = []
const onConfirm = jest.fn(() => callOrder.push('confirm'))
const onHide = jest.fn(() => callOrder.push('hide'))
const onConfirm = vi.fn(() => callOrder.push('confirm'))
const onHide = vi.fn(() => callOrder.push('hide'))
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -273,8 +274,8 @@ describe('StopEmbeddingModal', () => {
it('should handle multiple clicks on confirm button', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -294,8 +295,8 @@ describe('StopEmbeddingModal', () => {
describe('Cancel Button', () => {
it('should call onHide when cancel button is clicked', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -310,8 +311,8 @@ describe('StopEmbeddingModal', () => {
it('should not call onConfirm when cancel button is clicked', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -326,8 +327,8 @@ describe('StopEmbeddingModal', () => {
it('should handle multiple clicks on cancel button', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -346,8 +347,8 @@ describe('StopEmbeddingModal', () => {
describe('Close Icon', () => {
it('should call onHide when close span is clicked', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
const { container } = renderStopEmbeddingModal({ onConfirm, onHide })
// Act - Find the close span (it should be the span with onClick handler)
@ -372,8 +373,8 @@ describe('StopEmbeddingModal', () => {
it('should not call onConfirm when close span is clicked', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
const { container } = renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -396,8 +397,8 @@ describe('StopEmbeddingModal', () => {
describe('Different Close Methods', () => {
it('should distinguish between confirm and cancel actions', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act - Click cancel
@ -411,7 +412,7 @@ describe('StopEmbeddingModal', () => {
expect(onHide).toHaveBeenCalledTimes(1)
// Reset
jest.clearAllMocks()
vi.clearAllMocks()
// Act - Click confirm
const confirmButton = screen.getByText('datasetCreation.stepThree.modelButtonConfirm')
@ -432,8 +433,8 @@ describe('StopEmbeddingModal', () => {
describe('Edge Cases', () => {
it('should handle rapid confirm button clicks', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act - Rapid clicks
@ -450,8 +451,8 @@ describe('StopEmbeddingModal', () => {
it('should handle rapid cancel button clicks', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act - Rapid clicks
@ -468,10 +469,10 @@ describe('StopEmbeddingModal', () => {
it('should handle callbacks being replaced', async () => {
// Arrange
const onConfirm1 = jest.fn()
const onHide1 = jest.fn()
const onConfirm2 = jest.fn()
const onHide2 = jest.fn()
const onConfirm1 = vi.fn()
const onHide1 = vi.fn()
const onConfirm2 = vi.fn()
const onHide2 = vi.fn()
// Act
const { rerender } = render(
@ -501,8 +502,8 @@ describe('StopEmbeddingModal', () => {
render(
<StopEmbeddingModal
show={true}
onConfirm={jest.fn()}
onHide={jest.fn()}
onConfirm={vi.fn()}
onHide={vi.fn()}
/>,
)
@ -553,10 +554,10 @@ describe('StopEmbeddingModal', () => {
let confirmTime = 0
let hideTime = 0
let counter = 0
const onConfirm = jest.fn(() => {
const onConfirm = vi.fn(() => {
confirmTime = ++counter
})
const onHide = jest.fn(() => {
const onHide = vi.fn(() => {
hideTime = ++counter
})
renderStopEmbeddingModal({ onConfirm, onHide })
@ -574,8 +575,8 @@ describe('StopEmbeddingModal', () => {
it('should call both callbacks exactly once per click', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -591,8 +592,8 @@ describe('StopEmbeddingModal', () => {
it('should pass no arguments to onConfirm', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -607,8 +608,8 @@ describe('StopEmbeddingModal', () => {
it('should pass no arguments to onHide when called from submit', async () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -629,7 +630,7 @@ describe('StopEmbeddingModal', () => {
it('should pass show prop to Modal as isShow', async () => {
// Arrange & Act
const { rerender } = render(
<StopEmbeddingModal show={true} onConfirm={jest.fn()} onHide={jest.fn()} />,
<StopEmbeddingModal show={true} onConfirm={vi.fn()} onHide={vi.fn()} />,
)
// Assert - Modal should be visible
@ -637,7 +638,7 @@ describe('StopEmbeddingModal', () => {
// Act - Hide modal
await act(async () => {
rerender(<StopEmbeddingModal show={false} onConfirm={jest.fn()} onHide={jest.fn()} />)
rerender(<StopEmbeddingModal show={false} onConfirm={vi.fn()} onHide={vi.fn()} />)
})
// Assert - Modal should transition to hidden (wait for transition)
@ -689,8 +690,8 @@ describe('StopEmbeddingModal', () => {
describe('Component Lifecycle', () => {
it('should unmount cleanly', () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
const { unmount } = renderStopEmbeddingModal({ onConfirm, onHide })
// Act & Assert - Should not throw
@ -699,8 +700,8 @@ describe('StopEmbeddingModal', () => {
it('should not call callbacks after unmount', () => {
// Arrange
const onConfirm = jest.fn()
const onHide = jest.fn()
const onConfirm = vi.fn()
const onHide = vi.fn()
const { unmount } = renderStopEmbeddingModal({ onConfirm, onHide })
// Act
@ -713,10 +714,10 @@ describe('StopEmbeddingModal', () => {
it('should re-render correctly when props update', async () => {
// Arrange
const onConfirm1 = jest.fn()
const onHide1 = jest.fn()
const onConfirm2 = jest.fn()
const onHide2 = jest.fn()
const onConfirm1 = vi.fn()
const onHide1 = vi.fn()
const onConfirm2 = vi.fn()
const onHide2 = vi.fn()
// Act - Initial render
const { rerender } = render(

View File

@ -2,13 +2,13 @@ import { render, screen } from '@testing-library/react'
import { TopBar, type TopBarProps } from './index'
// Mock next/link to capture href values
jest.mock('next/link', () => {
return ({ children, href, replace, className }: { children: React.ReactNode; href: string; replace?: boolean; className?: string }) => (
vi.mock('next/link', () => ({
default: ({ children, href, replace, className }: { children: React.ReactNode; href: string; replace?: boolean; className?: string }) => (
<a href={href} data-replace={replace} className={className} data-testid="back-link">
{children}
</a>
)
})
),
}))
// Helper to render TopBar with default props
const renderTopBar = (props: Partial<TopBarProps> = {}) => {
@ -27,7 +27,7 @@ const renderTopBar = (props: Partial<TopBarProps> = {}) => {
// ============================================================================
describe('TopBar', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
// --------------------------------------------------------------------------

View File

@ -24,12 +24,12 @@ const createCrawlResultItem = (overrides: Partial<CrawlResultItem> = {}): CrawlR
describe('Input', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
const createInputProps = (overrides: Partial<Parameters<typeof Input>[0]> = {}) => ({
value: '',
onChange: jest.fn(),
onChange: vi.fn(),
...overrides,
})
@ -70,7 +70,7 @@ describe('Input', () => {
describe('Text Input Behavior', () => {
it('should call onChange with string value for text input', async () => {
const onChange = jest.fn()
const onChange = vi.fn()
const props = createInputProps({ onChange })
render(<Input {...props} />)
@ -88,7 +88,7 @@ describe('Input', () => {
describe('Number Input Behavior', () => {
it('should call onChange with parsed integer for number input', () => {
const onChange = jest.fn()
const onChange = vi.fn()
const props = createInputProps({ isNumber: true, onChange, value: 0 })
render(<Input {...props} />)
@ -100,7 +100,7 @@ describe('Input', () => {
})
it('should call onChange with empty string when input is NaN', () => {
const onChange = jest.fn()
const onChange = vi.fn()
const props = createInputProps({ isNumber: true, onChange, value: 0 })
render(<Input {...props} />)
@ -112,7 +112,7 @@ describe('Input', () => {
})
it('should call onChange with empty string when input is empty', () => {
const onChange = jest.fn()
const onChange = vi.fn()
const props = createInputProps({ isNumber: true, onChange, value: 5 })
render(<Input {...props} />)
@ -124,7 +124,7 @@ describe('Input', () => {
})
it('should clamp negative values to MIN_VALUE (0)', () => {
const onChange = jest.fn()
const onChange = vi.fn()
const props = createInputProps({ isNumber: true, onChange, value: 0 })
render(<Input {...props} />)
@ -136,7 +136,7 @@ describe('Input', () => {
})
it('should handle decimal input by parsing as integer', () => {
const onChange = jest.fn()
const onChange = vi.fn()
const props = createInputProps({ isNumber: true, onChange, value: 0 })
render(<Input {...props} />)
@ -237,7 +237,7 @@ describe('Header', () => {
describe('User Interactions', () => {
it('should call onClickConfiguration when button is clicked', async () => {
const onClickConfiguration = jest.fn()
const onClickConfiguration = vi.fn()
const props = createHeaderProps({ onClickConfiguration })
render(<Header {...props} />)
@ -263,8 +263,8 @@ describe('CrawledResultItem', () => {
payload: createCrawlResultItem(),
isChecked: false,
isPreview: false,
onCheckChange: jest.fn(),
onPreview: jest.fn(),
onCheckChange: vi.fn(),
onPreview: vi.fn(),
testId: 'test-item',
...overrides,
})
@ -302,7 +302,7 @@ describe('CrawledResultItem', () => {
describe('Checkbox Behavior', () => {
it('should call onCheckChange with true when unchecked item is clicked', async () => {
const onCheckChange = jest.fn()
const onCheckChange = vi.fn()
const props = createItemProps({ isChecked: false, onCheckChange })
render(<CrawledResultItem {...props} />)
@ -313,7 +313,7 @@ describe('CrawledResultItem', () => {
})
it('should call onCheckChange with false when checked item is clicked', async () => {
const onCheckChange = jest.fn()
const onCheckChange = vi.fn()
const props = createItemProps({ isChecked: true, onCheckChange })
render(<CrawledResultItem {...props} />)
@ -326,7 +326,7 @@ describe('CrawledResultItem', () => {
describe('Preview Behavior', () => {
it('should call onPreview when preview button is clicked', async () => {
const onPreview = jest.fn()
const onPreview = vi.fn()
const props = createItemProps({ onPreview })
render(<CrawledResultItem {...props} />)
@ -371,8 +371,8 @@ describe('CrawledResult', () => {
createCrawlResultItem({ source_url: 'https://page3.com', title: 'Page 3' }),
],
checkedList: [],
onSelectedChange: jest.fn(),
onPreview: jest.fn(),
onSelectedChange: vi.fn(),
onPreview: vi.fn(),
usedTime: 2.5,
...overrides,
})
@ -420,7 +420,7 @@ describe('CrawledResult', () => {
describe('Select All / Deselect All', () => {
it('should call onSelectedChange with all items when select all is clicked', async () => {
const onSelectedChange = jest.fn()
const onSelectedChange = vi.fn()
const list = [
createCrawlResultItem({ source_url: 'https://page1.com' }),
createCrawlResultItem({ source_url: 'https://page2.com' }),
@ -434,7 +434,7 @@ describe('CrawledResult', () => {
})
it('should call onSelectedChange with empty array when reset all is clicked', async () => {
const onSelectedChange = jest.fn()
const onSelectedChange = vi.fn()
const list = [
createCrawlResultItem({ source_url: 'https://page1.com' }),
createCrawlResultItem({ source_url: 'https://page2.com' }),
@ -450,7 +450,7 @@ describe('CrawledResult', () => {
describe('Individual Item Selection', () => {
it('should add item to checkedList when unchecked item is checked', async () => {
const onSelectedChange = jest.fn()
const onSelectedChange = vi.fn()
const list = [
createCrawlResultItem({ source_url: 'https://page1.com', title: 'Page 1' }),
createCrawlResultItem({ source_url: 'https://page2.com', title: 'Page 2' }),
@ -464,7 +464,7 @@ describe('CrawledResult', () => {
})
it('should remove item from checkedList when checked item is unchecked', async () => {
const onSelectedChange = jest.fn()
const onSelectedChange = vi.fn()
const list = [
createCrawlResultItem({ source_url: 'https://page1.com', title: 'Page 1' }),
createCrawlResultItem({ source_url: 'https://page2.com', title: 'Page 2' }),
@ -478,7 +478,7 @@ describe('CrawledResult', () => {
})
it('should preserve other checked items when unchecking one item', async () => {
const onSelectedChange = jest.fn()
const onSelectedChange = vi.fn()
const list = [
createCrawlResultItem({ source_url: 'https://page1.com', title: 'Page 1' }),
createCrawlResultItem({ source_url: 'https://page2.com', title: 'Page 2' }),
@ -496,7 +496,7 @@ describe('CrawledResult', () => {
describe('Preview Behavior', () => {
it('should call onPreview with correct item when preview is clicked', async () => {
const onPreview = jest.fn()
const onPreview = vi.fn()
const list = [
createCrawlResultItem({ source_url: 'https://page1.com', title: 'Page 1' }),
createCrawlResultItem({ source_url: 'https://page2.com', title: 'Page 2' }),
@ -513,7 +513,7 @@ describe('CrawledResult', () => {
})
it('should track preview index correctly', async () => {
const onPreview = jest.fn()
const onPreview = vi.fn()
const list = [
createCrawlResultItem({ source_url: 'https://page1.com', title: 'Page 1' }),
createCrawlResultItem({ source_url: 'https://page2.com', title: 'Page 2' }),

View File

@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event'
import UrlInput from './base/url-input'
// Mock doc link context
jest.mock('@/context/i18n', () => ({
vi.mock('@/context/i18n', () => ({
useDocLink: () => () => 'https://docs.example.com',
}))
@ -13,13 +13,13 @@ jest.mock('@/context/i18n', () => ({
describe('UrlInput', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
// Helper to create default props for UrlInput
const createUrlInputProps = (overrides: Partial<Parameters<typeof UrlInput>[0]> = {}) => ({
isRunning: false,
onRun: jest.fn(),
onRun: vi.fn(),
...overrides,
})
@ -78,7 +78,7 @@ describe('UrlInput', () => {
it('should show loading state on button when running', () => {
// Arrange
const onRun = jest.fn()
const onRun = vi.fn()
const props = createUrlInputProps({ isRunning: true, onRun })
// Act
@ -148,7 +148,7 @@ describe('UrlInput', () => {
describe('Button Click', () => {
it('should call onRun with URL when button is clicked', async () => {
// Arrange
const onRun = jest.fn()
const onRun = vi.fn()
const props = createUrlInputProps({ onRun })
// Act
@ -164,7 +164,7 @@ describe('UrlInput', () => {
it('should call onRun with empty string if no URL entered', async () => {
// Arrange
const onRun = jest.fn()
const onRun = vi.fn()
const props = createUrlInputProps({ onRun })
// Act
@ -177,7 +177,7 @@ describe('UrlInput', () => {
it('should not call onRun when isRunning is true', async () => {
// Arrange
const onRun = jest.fn()
const onRun = vi.fn()
const props = createUrlInputProps({ onRun, isRunning: true })
// Act
@ -191,7 +191,7 @@ describe('UrlInput', () => {
it('should not call onRun when already running', async () => {
// Arrange
const onRun = jest.fn()
const onRun = vi.fn()
// First render with isRunning=false, type URL, then rerender with isRunning=true
const { rerender } = render(<UrlInput isRunning={false} onRun={onRun} />)
@ -211,7 +211,7 @@ describe('UrlInput', () => {
it('should prevent multiple clicks when already running', async () => {
// Arrange
const onRun = jest.fn()
const onRun = vi.fn()
const props = createUrlInputProps({ onRun, isRunning: true })
// Act
@ -250,8 +250,8 @@ describe('UrlInput', () => {
it('should call updated onRun callback after prop change', async () => {
// Arrange
const onRun1 = jest.fn()
const onRun2 = jest.fn()
const onRun1 = vi.fn()
const onRun2 = vi.fn()
// Act
const { rerender } = render(<UrlInput isRunning={false} onRun={onRun1} />)
@ -363,7 +363,7 @@ describe('UrlInput', () => {
it('should handle keyboard enter to trigger run', async () => {
// Arrange - Note: This tests if the button can be activated via keyboard
const onRun = jest.fn()
const onRun = vi.fn()
const props = createUrlInputProps({ onRun })
// Act
@ -382,7 +382,7 @@ describe('UrlInput', () => {
it('should handle empty URL submission', async () => {
// Arrange
const onRun = jest.fn()
const onRun = vi.fn()
const props = createUrlInputProps({ onRun })
// Act

View File

@ -1,3 +1,4 @@
import type { Mock } from 'vitest'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import JinaReader from './index'
@ -6,25 +7,25 @@ import { checkJinaReaderTaskStatus, createJinaReaderTask } from '@/service/datas
import { sleep } from '@/utils'
// Mock external dependencies
jest.mock('@/service/datasets', () => ({
createJinaReaderTask: jest.fn(),
checkJinaReaderTaskStatus: jest.fn(),
vi.mock('@/service/datasets', () => ({
createJinaReaderTask: vi.fn(),
checkJinaReaderTaskStatus: vi.fn(),
}))
jest.mock('@/utils', () => ({
sleep: jest.fn(() => Promise.resolve()),
vi.mock('@/utils', () => ({
sleep: vi.fn(() => Promise.resolve()),
}))
// Mock modal context
const mockSetShowAccountSettingModal = jest.fn()
jest.mock('@/context/modal-context', () => ({
const mockSetShowAccountSettingModal = vi.fn()
vi.mock('@/context/modal-context', () => ({
useModalContext: () => ({
setShowAccountSettingModal: mockSetShowAccountSettingModal,
}),
}))
// Mock doc link context
jest.mock('@/context/i18n', () => ({
vi.mock('@/context/i18n', () => ({
useDocLink: () => () => 'https://docs.example.com',
}))
@ -54,12 +55,12 @@ const createCrawlResultItem = (overrides: Partial<CrawlResultItem> = {}): CrawlR
})
const createDefaultProps = (overrides: Partial<Parameters<typeof JinaReader>[0]> = {}) => ({
onPreview: jest.fn(),
onPreview: vi.fn(),
checkedCrawlResult: [] as CrawlResultItem[],
onCheckedCrawlResultChange: jest.fn(),
onJobIdChange: jest.fn(),
onCheckedCrawlResultChange: vi.fn(),
onJobIdChange: vi.fn(),
crawlOptions: createDefaultCrawlOptions(),
onCrawlOptionsChange: jest.fn(),
onCrawlOptionsChange: vi.fn(),
...overrides,
})
@ -68,7 +69,7 @@ const createDefaultProps = (overrides: Partial<Parameters<typeof JinaReader>[0]>
// ============================================================================
describe('JinaReader', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
describe('Rendering', () => {
@ -158,7 +159,7 @@ describe('JinaReader', () => {
it('should call onCrawlOptionsChange when options change', async () => {
// Arrange
const user = userEvent.setup()
const onCrawlOptionsChange = jest.fn()
const onCrawlOptionsChange = vi.fn()
const props = createDefaultProps({ onCrawlOptionsChange })
// Act
@ -188,7 +189,7 @@ describe('JinaReader', () => {
it('should execute crawl task when checkedCrawlResult is provided', async () => {
// Arrange
const checkedItem = createCrawlResultItem({ source_url: 'https://checked.com' })
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: {
title: 'Test',
@ -234,7 +235,7 @@ describe('JinaReader', () => {
describe('State Management', () => {
it('should transition from init to running state when run is clicked', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
let resolvePromise: () => void
mockCreateTask.mockImplementation(() => new Promise((resolve) => {
resolvePromise = () => resolve({ data: { title: 'T', content: 'C', description: 'D', url: 'https://example.com' } })
@ -262,7 +263,7 @@ describe('JinaReader', () => {
it('should transition to finished state after successful crawl', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: {
title: 'Test Page',
@ -288,8 +289,8 @@ describe('JinaReader', () => {
it('should update crawl result state during polling', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job-123' })
mockCheckStatus
@ -310,8 +311,8 @@ describe('JinaReader', () => {
],
})
const onCheckedCrawlResultChange = jest.fn()
const onJobIdChange = jest.fn()
const onCheckedCrawlResultChange = vi.fn()
const onJobIdChange = vi.fn()
const props = createDefaultProps({ onCheckedCrawlResultChange, onJobIdChange })
// Act
@ -332,7 +333,7 @@ describe('JinaReader', () => {
it('should fold options when step changes from init', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: {
title: 'Test',
@ -367,9 +368,9 @@ describe('JinaReader', () => {
describe('Side Effects and Cleanup', () => {
it('should call sleep during polling', async () => {
// Arrange
const mockSleep = sleep as jest.Mock
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockSleep = sleep as Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
mockCheckStatus
@ -392,7 +393,7 @@ describe('JinaReader', () => {
it('should update controlFoldOptions when step changes', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockImplementation(() => new Promise((_resolve) => { /* pending */ }))
const props = createDefaultProps()
@ -439,7 +440,7 @@ describe('JinaReader', () => {
it('should memoize checkValid callback based on crawlOptions', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValue({ data: { title: 'T', content: 'C', description: 'D', url: 'https://a.com' } })
const props = createDefaultProps()
@ -483,7 +484,7 @@ describe('JinaReader', () => {
it('should handle URL input and run button click', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: {
title: 'Test',
@ -512,8 +513,8 @@ describe('JinaReader', () => {
it('should handle preview action on crawled result', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const onPreview = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const onPreview = vi.fn()
const crawlResultData = {
title: 'Preview Test',
content: '# Content',
@ -545,7 +546,7 @@ describe('JinaReader', () => {
it('should handle checkbox changes in options', async () => {
// Arrange
const onCrawlOptionsChange = jest.fn()
const onCrawlOptionsChange = vi.fn()
const props = createDefaultProps({
onCrawlOptionsChange,
crawlOptions: createDefaultCrawlOptions({ crawl_sub_pages: false }),
@ -593,7 +594,7 @@ describe('JinaReader', () => {
describe('API Calls', () => {
it('should call createJinaReaderTask with correct parameters', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'https://api-test.com' },
})
@ -618,8 +619,8 @@ describe('JinaReader', () => {
it('should handle direct data response from API', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({
data: {
@ -651,9 +652,9 @@ describe('JinaReader', () => {
it('should handle job_id response and poll for status', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const onJobIdChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
const onJobIdChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'poll-job-123' })
mockCheckStatus.mockResolvedValueOnce({
@ -686,8 +687,8 @@ describe('JinaReader', () => {
it('should handle failed status from polling', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'fail-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -713,8 +714,8 @@ describe('JinaReader', () => {
it('should handle API error during status check', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'error-job' })
mockCheckStatus.mockRejectedValueOnce({
@ -737,9 +738,9 @@ describe('JinaReader', () => {
it('should limit total to crawlOptions.limit', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'limit-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -832,7 +833,7 @@ describe('JinaReader', () => {
it('should accept URL with http:// protocol', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'http://example.com' },
})
@ -907,10 +908,10 @@ describe('JinaReader', () => {
it('should handle API throwing an exception', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockRejectedValueOnce(new Error('Network error'))
// Suppress console output during test to avoid noisy logs
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(vi.fn())
const props = createDefaultProps()
@ -930,8 +931,8 @@ describe('JinaReader', () => {
it('should handle status response without status field', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'no-status-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -955,8 +956,8 @@ describe('JinaReader', () => {
it('should show unknown error when error message is empty', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'empty-error-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -980,9 +981,9 @@ describe('JinaReader', () => {
it('should handle empty data array from API', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'empty-data-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1008,9 +1009,9 @@ describe('JinaReader', () => {
it('should handle null data from running status', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'null-data-job' })
mockCheckStatus
@ -1043,9 +1044,9 @@ describe('JinaReader', () => {
it('should return empty array when completed job has undefined data', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'undefined-data-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1071,8 +1072,8 @@ describe('JinaReader', () => {
it('should show zero current progress when crawlResult is not yet available', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'zero-current-job' })
mockCheckStatus.mockImplementation(() => new Promise(() => { /* never resolves */ }))
@ -1095,8 +1096,8 @@ describe('JinaReader', () => {
it('should show 0/0 progress when limit is zero string', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'zero-total-job' })
mockCheckStatus.mockImplementation(() => new Promise(() => { /* never resolves */ }))
@ -1119,9 +1120,9 @@ describe('JinaReader', () => {
it('should complete successfully when result data is undefined', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'undefined-result-data-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1148,8 +1149,8 @@ describe('JinaReader', () => {
it('should use limit as total when crawlResult total is not available', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'no-total-job' })
mockCheckStatus.mockImplementation(() => new Promise(() => { /* never resolves */ }))
@ -1172,8 +1173,8 @@ describe('JinaReader', () => {
it('should fallback to limit when crawlResult has zero total', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'both-zero-job' })
mockCheckStatus
@ -1203,8 +1204,8 @@ describe('JinaReader', () => {
it('should construct result item from direct data response', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({
data: {
@ -1241,7 +1242,7 @@ describe('JinaReader', () => {
describe('Prop Variations', () => {
it('should handle different limit values in crawlOptions', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'https://limit.com' },
})
@ -1268,7 +1269,7 @@ describe('JinaReader', () => {
it('should handle different max_depth values', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'https://depth.com' },
})
@ -1295,7 +1296,7 @@ describe('JinaReader', () => {
it('should handle crawl_sub_pages disabled', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'https://nosub.com' },
})
@ -1322,7 +1323,7 @@ describe('JinaReader', () => {
it('should handle use_sitemap enabled', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'https://sitemap.com' },
})
@ -1349,7 +1350,7 @@ describe('JinaReader', () => {
it('should handle includes and excludes patterns', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'https://patterns.com' },
})
@ -1382,7 +1383,7 @@ describe('JinaReader', () => {
it('should handle pre-selected crawl results', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const existingResult = createCrawlResultItem({ source_url: 'https://existing.com' })
mockCreateTask.mockResolvedValueOnce({
@ -1407,7 +1408,7 @@ describe('JinaReader', () => {
it('should handle string type limit value', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'https://string-limit.com' },
})
@ -1435,8 +1436,8 @@ describe('JinaReader', () => {
describe('Display and UI States', () => {
it('should show crawling progress during running state', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'progress-job' })
mockCheckStatus.mockImplementation(() => new Promise((_resolve) => { /* pending */ })) // Never resolves
@ -1459,7 +1460,7 @@ describe('JinaReader', () => {
it('should display time consumed after crawl completion', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: { title: 'T', content: 'C', description: 'D', url: 'https://time.com' },
@ -1481,7 +1482,7 @@ describe('JinaReader', () => {
it('should display crawled results list after completion', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockResolvedValueOnce({
data: {
@ -1508,11 +1509,11 @@ describe('JinaReader', () => {
it('should show error message component when crawl fails', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCreateTask = createJinaReaderTask as Mock
mockCreateTask.mockRejectedValueOnce(new Error('Failed'))
// Suppress console output during test to avoid noisy logs
jest.spyOn(console, 'log').mockImplementation(jest.fn())
vi.spyOn(console, 'log').mockImplementation(vi.fn())
const props = createDefaultProps()
@ -1535,11 +1536,11 @@ describe('JinaReader', () => {
describe('Integration', () => {
it('should complete full crawl workflow with job polling', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const mockCheckStatus = checkJinaReaderTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const onJobIdChange = jest.fn()
const onPreview = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const mockCheckStatus = checkJinaReaderTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
const onJobIdChange = vi.fn()
const onPreview = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'full-workflow-job' })
mockCheckStatus
@ -1600,8 +1601,8 @@ describe('JinaReader', () => {
it('should handle select all and deselect all in results', async () => {
// Arrange
const mockCreateTask = createJinaReaderTask as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createJinaReaderTask as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({
data: { title: 'Single', content: 'C', description: 'D', url: 'https://single.com' },

View File

@ -1,3 +1,4 @@
import type { Mock } from 'vitest'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import WaterCrawl from './index'
@ -6,18 +7,18 @@ import { checkWatercrawlTaskStatus, createWatercrawlTask } from '@/service/datas
import { sleep } from '@/utils'
// Mock external dependencies
jest.mock('@/service/datasets', () => ({
createWatercrawlTask: jest.fn(),
checkWatercrawlTaskStatus: jest.fn(),
vi.mock('@/service/datasets', () => ({
createWatercrawlTask: vi.fn(),
checkWatercrawlTaskStatus: vi.fn(),
}))
jest.mock('@/utils', () => ({
sleep: jest.fn(() => Promise.resolve()),
vi.mock('@/utils', () => ({
sleep: vi.fn(() => Promise.resolve()),
}))
// Mock modal context
const mockSetShowAccountSettingModal = jest.fn()
jest.mock('@/context/modal-context', () => ({
const mockSetShowAccountSettingModal = vi.fn()
vi.mock('@/context/modal-context', () => ({
useModalContext: () => ({
setShowAccountSettingModal: mockSetShowAccountSettingModal,
}),
@ -49,12 +50,12 @@ const createCrawlResultItem = (overrides: Partial<CrawlResultItem> = {}): CrawlR
})
const createDefaultProps = (overrides: Partial<Parameters<typeof WaterCrawl>[0]> = {}) => ({
onPreview: jest.fn(),
onPreview: vi.fn(),
checkedCrawlResult: [] as CrawlResultItem[],
onCheckedCrawlResultChange: jest.fn(),
onJobIdChange: jest.fn(),
onCheckedCrawlResultChange: vi.fn(),
onJobIdChange: vi.fn(),
crawlOptions: createDefaultCrawlOptions(),
onCrawlOptionsChange: jest.fn(),
onCrawlOptionsChange: vi.fn(),
...overrides,
})
@ -63,7 +64,7 @@ const createDefaultProps = (overrides: Partial<Parameters<typeof WaterCrawl>[0]>
// ============================================================================
describe('WaterCrawl', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})
// Tests for initial component rendering
@ -154,7 +155,7 @@ describe('WaterCrawl', () => {
it('should call onCrawlOptionsChange when options change', async () => {
// Arrange
const user = userEvent.setup()
const onCrawlOptionsChange = jest.fn()
const onCrawlOptionsChange = vi.fn()
const props = createDefaultProps({ onCrawlOptionsChange })
// Act
@ -184,10 +185,10 @@ describe('WaterCrawl', () => {
it('should execute crawl task when checkedCrawlResult is provided', async () => {
// Arrange
const checkedItem = createCrawlResultItem({ source_url: 'https://checked.com' })
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCheckStatus.mockResolvedValueOnce({
status: 'completed',
current: 1,
@ -231,7 +232,7 @@ describe('WaterCrawl', () => {
describe('State Management', () => {
it('should transition from init to running state when run is clicked', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
let resolvePromise: () => void
mockCreateTask.mockImplementation(() => new Promise((resolve) => {
resolvePromise = () => resolve({ job_id: 'test-job' })
@ -259,8 +260,8 @@ describe('WaterCrawl', () => {
it('should transition to finished state after successful crawl', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -286,8 +287,8 @@ describe('WaterCrawl', () => {
it('should update crawl result state during polling', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job-123' })
mockCheckStatus
@ -308,8 +309,8 @@ describe('WaterCrawl', () => {
],
})
const onCheckedCrawlResultChange = jest.fn()
const onJobIdChange = jest.fn()
const onCheckedCrawlResultChange = vi.fn()
const onJobIdChange = vi.fn()
const props = createDefaultProps({ onCheckedCrawlResultChange, onJobIdChange })
// Act
@ -330,8 +331,8 @@ describe('WaterCrawl', () => {
it('should fold options when step changes from init', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -366,9 +367,9 @@ describe('WaterCrawl', () => {
describe('Side Effects and Cleanup', () => {
it('should call sleep during polling', async () => {
// Arrange
const mockSleep = sleep as jest.Mock
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockSleep = sleep as Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
mockCheckStatus
@ -391,7 +392,7 @@ describe('WaterCrawl', () => {
it('should update controlFoldOptions when step changes', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
mockCreateTask.mockImplementation(() => new Promise(() => { /* pending */ }))
const props = createDefaultProps()
@ -438,8 +439,8 @@ describe('WaterCrawl', () => {
it('should memoize checkValid callback based on crawlOptions', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValue({ job_id: 'test-job' })
mockCheckStatus.mockResolvedValue({
@ -490,8 +491,8 @@ describe('WaterCrawl', () => {
it('should handle URL input and run button click', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -520,9 +521,9 @@ describe('WaterCrawl', () => {
it('should handle preview action on crawled result', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onPreview = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onPreview = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -554,7 +555,7 @@ describe('WaterCrawl', () => {
it('should handle checkbox changes in options', async () => {
// Arrange
const onCrawlOptionsChange = jest.fn()
const onCrawlOptionsChange = vi.fn()
const props = createDefaultProps({
onCrawlOptionsChange,
crawlOptions: createDefaultCrawlOptions({ crawl_sub_pages: false }),
@ -602,8 +603,8 @@ describe('WaterCrawl', () => {
describe('API Calls', () => {
it('should call createWatercrawlTask with correct parameters', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'api-test-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -633,8 +634,8 @@ describe('WaterCrawl', () => {
it('should delete max_depth from options when it is empty string', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'test-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -662,9 +663,9 @@ describe('WaterCrawl', () => {
it('should poll for status with job_id', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onJobIdChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onJobIdChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'poll-job-123' })
mockCheckStatus.mockResolvedValueOnce({
@ -697,8 +698,8 @@ describe('WaterCrawl', () => {
it('should handle error status from polling', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'fail-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -724,8 +725,8 @@ describe('WaterCrawl', () => {
it('should handle API error during status check', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'error-job' })
mockCheckStatus.mockRejectedValueOnce({
@ -748,9 +749,9 @@ describe('WaterCrawl', () => {
it('should limit total to crawlOptions.limit', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'limit-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -781,8 +782,8 @@ describe('WaterCrawl', () => {
it('should handle response without status field as error', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'no-status-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -868,8 +869,8 @@ describe('WaterCrawl', () => {
it('should accept URL with http:// protocol', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'http-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -949,10 +950,10 @@ describe('WaterCrawl', () => {
it('should handle API throwing an exception', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
mockCreateTask.mockRejectedValueOnce(new Error('Network error'))
// Suppress console output during test to avoid noisy logs
const consoleSpy = jest.spyOn(console, 'log').mockImplementation(jest.fn())
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(vi.fn())
const props = createDefaultProps()
@ -972,8 +973,8 @@ describe('WaterCrawl', () => {
it('should show unknown error when error message is empty', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'empty-error-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -997,9 +998,9 @@ describe('WaterCrawl', () => {
it('should handle empty data array from API', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'empty-data-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1025,9 +1026,9 @@ describe('WaterCrawl', () => {
it('should handle null data from running status', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'null-data-job' })
mockCheckStatus
@ -1060,9 +1061,9 @@ describe('WaterCrawl', () => {
it('should handle undefined data from completed job polling', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'undefined-data-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1088,8 +1089,8 @@ describe('WaterCrawl', () => {
it('should handle crawlResult with zero current value', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'zero-current-job' })
mockCheckStatus.mockImplementation(() => new Promise(() => { /* never resolves */ }))
@ -1112,8 +1113,8 @@ describe('WaterCrawl', () => {
it('should handle crawlResult with zero total and empty limit', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'zero-total-job' })
mockCheckStatus.mockImplementation(() => new Promise(() => { /* never resolves */ }))
@ -1136,9 +1137,9 @@ describe('WaterCrawl', () => {
it('should handle undefined crawlResult data in finished state', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'undefined-result-data-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1165,8 +1166,8 @@ describe('WaterCrawl', () => {
it('should use parseFloat fallback when crawlResult.total is undefined', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'no-total-job' })
mockCheckStatus.mockImplementation(() => new Promise(() => { /* never resolves */ }))
@ -1189,8 +1190,8 @@ describe('WaterCrawl', () => {
it('should handle crawlResult with current=0 and total=0 during running', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'both-zero-job' })
mockCheckStatus
@ -1225,8 +1226,8 @@ describe('WaterCrawl', () => {
describe('Prop Variations', () => {
it('should handle different limit values in crawlOptions', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'limit-var-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1258,8 +1259,8 @@ describe('WaterCrawl', () => {
it('should handle different max_depth values', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'depth-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1291,8 +1292,8 @@ describe('WaterCrawl', () => {
it('should handle crawl_sub_pages disabled', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'nosub-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1324,8 +1325,8 @@ describe('WaterCrawl', () => {
it('should handle use_sitemap enabled', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'sitemap-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1357,8 +1358,8 @@ describe('WaterCrawl', () => {
it('should handle includes and excludes patterns', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'patterns-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1396,8 +1397,8 @@ describe('WaterCrawl', () => {
it('should handle pre-selected crawl results', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const existingResult = createCrawlResultItem({ source_url: 'https://existing.com' })
mockCreateTask.mockResolvedValueOnce({ job_id: 'preselect-job' })
@ -1426,8 +1427,8 @@ describe('WaterCrawl', () => {
it('should handle string type limit value', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'string-limit-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1455,8 +1456,8 @@ describe('WaterCrawl', () => {
it('should handle only_main_content option', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'main-content-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1493,8 +1494,8 @@ describe('WaterCrawl', () => {
describe('Display and UI States', () => {
it('should show crawling progress during running state', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'progress-job' })
mockCheckStatus.mockImplementation(() => new Promise(() => { /* pending */ }))
@ -1517,8 +1518,8 @@ describe('WaterCrawl', () => {
it('should display time consumed after crawl completion', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'time-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1545,8 +1546,8 @@ describe('WaterCrawl', () => {
it('should display crawled results list after completion', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
mockCreateTask.mockResolvedValueOnce({ job_id: 'result-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1572,11 +1573,11 @@ describe('WaterCrawl', () => {
it('should show error message component when crawl fails', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCreateTask = createWatercrawlTask as Mock
mockCreateTask.mockRejectedValueOnce(new Error('Failed'))
// Suppress console output during test to avoid noisy logs
jest.spyOn(console, 'log').mockImplementation(jest.fn())
vi.spyOn(console, 'log').mockImplementation(vi.fn())
const props = createDefaultProps()
@ -1594,9 +1595,9 @@ describe('WaterCrawl', () => {
it('should update progress during multiple polling iterations', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'multi-poll-job' })
mockCheckStatus
@ -1659,11 +1660,11 @@ describe('WaterCrawl', () => {
describe('Integration', () => {
it('should complete full crawl workflow with job polling', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const onJobIdChange = jest.fn()
const onPreview = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
const onJobIdChange = vi.fn()
const onPreview = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'full-workflow-job' })
mockCheckStatus
@ -1724,9 +1725,9 @@ describe('WaterCrawl', () => {
it('should handle select all and deselect all in results', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onCheckedCrawlResultChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onCheckedCrawlResultChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'select-all-job' })
mockCheckStatus.mockResolvedValueOnce({
@ -1759,11 +1760,11 @@ describe('WaterCrawl', () => {
it('should handle complete workflow from input to preview', async () => {
// Arrange
const mockCreateTask = createWatercrawlTask as jest.Mock
const mockCheckStatus = checkWatercrawlTaskStatus as jest.Mock
const onPreview = jest.fn()
const onCheckedCrawlResultChange = jest.fn()
const onJobIdChange = jest.fn()
const mockCreateTask = createWatercrawlTask as Mock
const mockCheckStatus = checkWatercrawlTaskStatus as Mock
const onPreview = vi.fn()
const onCheckedCrawlResultChange = vi.fn()
const onJobIdChange = vi.fn()
mockCreateTask.mockResolvedValueOnce({ job_id: 'preview-workflow-job' })
mockCheckStatus.mockResolvedValueOnce({