mirror of
https://github.com/langgenius/dify.git
synced 2026-04-05 16:59:21 +08:00
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:
@@ -11,7 +11,7 @@ enum MockCredentialTypeEnum {
|
||||
}
|
||||
|
||||
// Mock plugin-auth module to avoid deep import chain issues
|
||||
jest.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
vi.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
CredentialTypeEnum: {
|
||||
OAUTH2: 'oauth2',
|
||||
API_KEY: 'api_key',
|
||||
@@ -19,7 +19,7 @@ jest.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
}))
|
||||
|
||||
// Mock portal-to-follow-elem - use React state to properly handle open/close
|
||||
jest.mock('@/app/components/base/portal-to-follow-elem', () => {
|
||||
vi.mock('@/app/components/base/portal-to-follow-elem', () => {
|
||||
const MockPortalToFollowElem = ({ children, open }: any) => {
|
||||
return (
|
||||
<div data-testid="portal-root" data-open={open}>
|
||||
@@ -85,14 +85,14 @@ const createMockCredentials = (count: number = 3): DataSourceCredential[] =>
|
||||
|
||||
const createDefaultProps = (overrides?: Partial<CredentialSelectorProps>): CredentialSelectorProps => ({
|
||||
currentCredentialId: 'cred-1',
|
||||
onCredentialChange: jest.fn(),
|
||||
onCredentialChange: vi.fn(),
|
||||
credentials: createMockCredentials(),
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('CredentialSelector', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// ==========================================
|
||||
@@ -277,7 +277,7 @@ describe('CredentialSelector', () => {
|
||||
describe('onCredentialChange prop', () => {
|
||||
it('should be called when selecting a credential', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
render(<CredentialSelector {...props} />)
|
||||
|
||||
@@ -298,7 +298,7 @@ describe('CredentialSelector', () => {
|
||||
['cred-3', 'Credential 3'],
|
||||
])('should call onCredentialChange with %s when selecting %s', (credId, credentialName) => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
render(<CredentialSelector {...props} />)
|
||||
|
||||
@@ -317,7 +317,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should call onCredentialChange with cred-1 when selecting Credential 1 in dropdown', () => {
|
||||
// Arrange - Start with cred-2 selected so cred-1 is only in dropdown
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
onCredentialChange: mockOnChange,
|
||||
currentCredentialId: 'cred-2',
|
||||
@@ -359,7 +359,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should call onCredentialChange when clicking a credential item', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
render(<CredentialSelector {...props} />)
|
||||
|
||||
@@ -376,7 +376,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should close dropdown after selecting a credential', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
render(<CredentialSelector {...props} />)
|
||||
|
||||
@@ -410,7 +410,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should allow selecting credentials multiple times', () => {
|
||||
// Arrange - Start with cred-2 selected so we can select other credentials
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
onCredentialChange: mockOnChange,
|
||||
currentCredentialId: 'cred-2',
|
||||
@@ -435,7 +435,7 @@ describe('CredentialSelector', () => {
|
||||
describe('Side Effects and Cleanup', () => {
|
||||
it('should auto-select first credential when currentCredential is not found and credentials exist', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
currentCredentialId: 'non-existent-id',
|
||||
onCredentialChange: mockOnChange,
|
||||
@@ -450,7 +450,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should not call onCredentialChange when currentCredential is found', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
currentCredentialId: 'cred-2',
|
||||
onCredentialChange: mockOnChange,
|
||||
@@ -465,7 +465,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should not call onCredentialChange when credentials array is empty', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
currentCredentialId: 'cred-1',
|
||||
credentials: [],
|
||||
@@ -481,7 +481,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should auto-select when credentials change and currentCredential becomes invalid', async () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const initialCredentials = createMockCredentials(3)
|
||||
const props = createDefaultProps({
|
||||
currentCredentialId: 'cred-1',
|
||||
@@ -512,7 +512,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should not trigger auto-select effect on every render with same props', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
|
||||
// Act - Render and rerender with same props
|
||||
@@ -531,7 +531,7 @@ describe('CredentialSelector', () => {
|
||||
describe('Callback Stability and Memoization', () => {
|
||||
it('should have stable handleCredentialChange callback', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
render(<CredentialSelector {...props} />)
|
||||
|
||||
@@ -547,8 +547,8 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should update handleCredentialChange when onCredentialChange changes', () => {
|
||||
// Arrange
|
||||
const mockOnChange1 = jest.fn()
|
||||
const mockOnChange2 = jest.fn()
|
||||
const mockOnChange1 = vi.fn()
|
||||
const mockOnChange2 = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange1 })
|
||||
|
||||
const { rerender } = render(<CredentialSelector {...props} />)
|
||||
@@ -618,7 +618,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should return undefined currentCredential when id not found', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
currentCredentialId: 'non-existent',
|
||||
onCredentialChange: mockOnChange,
|
||||
@@ -643,9 +643,9 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should not re-render when props remain the same', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
const renderSpy = jest.fn()
|
||||
const renderSpy = vi.fn()
|
||||
|
||||
const TrackedCredentialSelector: React.FC<CredentialSelectorProps> = (trackedProps) => {
|
||||
renderSpy()
|
||||
@@ -693,8 +693,8 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should re-render when onCredentialChange reference changes', () => {
|
||||
// Arrange
|
||||
const mockOnChange1 = jest.fn()
|
||||
const mockOnChange2 = jest.fn()
|
||||
const mockOnChange1 = vi.fn()
|
||||
const mockOnChange2 = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange1 })
|
||||
const { rerender } = render(<CredentialSelector {...props} />)
|
||||
|
||||
@@ -845,7 +845,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should handle credential selection with duplicate names', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const duplicateCredentials = [
|
||||
createMockCredential({ id: 'cred-1', name: 'Same Name' }),
|
||||
createMockCredential({ id: 'cred-2', name: 'Same Name' }),
|
||||
@@ -875,7 +875,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should not crash when clicking credential after unmount', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
const { unmount } = render(<CredentialSelector {...props} />)
|
||||
|
||||
@@ -1017,7 +1017,7 @@ describe('CredentialSelector', () => {
|
||||
|
||||
it('should pass handleCredentialChange to List component', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
render(<CredentialSelector {...props} />)
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ enum MockCredentialTypeEnum {
|
||||
}
|
||||
|
||||
// Mock plugin-auth module to avoid deep import chain issues
|
||||
jest.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
vi.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
CredentialTypeEnum: {
|
||||
OAUTH2: 'oauth2',
|
||||
API_KEY: 'api_key',
|
||||
@@ -18,7 +18,7 @@ jest.mock('@/app/components/plugins/plugin-auth', () => ({
|
||||
}))
|
||||
|
||||
// Mock portal-to-follow-elem - required for CredentialSelector
|
||||
jest.mock('@/app/components/base/portal-to-follow-elem', () => {
|
||||
vi.mock('@/app/components/base/portal-to-follow-elem', () => {
|
||||
const MockPortalToFollowElem = ({ children, open }: any) => {
|
||||
return (
|
||||
<div data-testid="portal-root" data-open={open}>
|
||||
@@ -84,14 +84,14 @@ const createDefaultProps = (overrides?: Partial<HeaderProps>): HeaderProps => ({
|
||||
docLink: 'https://docs.example.com',
|
||||
pluginName: 'Test Plugin',
|
||||
currentCredentialId: 'cred-1',
|
||||
onCredentialChange: jest.fn(),
|
||||
onCredentialChange: vi.fn(),
|
||||
credentials: createMockCredentials(),
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('Header', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// ==========================================
|
||||
@@ -266,7 +266,7 @@ describe('Header', () => {
|
||||
describe('onClickConfiguration prop', () => {
|
||||
it('should call onClickConfiguration when configuration icon is clicked', () => {
|
||||
// Arrange
|
||||
const mockOnClick = jest.fn()
|
||||
const mockOnClick = vi.fn()
|
||||
const props = createDefaultProps({ onClickConfiguration: mockOnClick })
|
||||
render(<Header {...props} />)
|
||||
|
||||
@@ -328,7 +328,7 @@ describe('Header', () => {
|
||||
|
||||
it('should pass onCredentialChange to CredentialSelector', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
render(<Header {...props} />)
|
||||
|
||||
@@ -363,7 +363,7 @@ describe('Header', () => {
|
||||
|
||||
it('should allow credential selection through CredentialSelector', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnChange })
|
||||
render(<Header {...props} />)
|
||||
|
||||
@@ -377,7 +377,7 @@ describe('Header', () => {
|
||||
|
||||
it('should trigger configuration callback when clicking config icon', () => {
|
||||
// Arrange
|
||||
const mockOnConfig = jest.fn()
|
||||
const mockOnConfig = vi.fn()
|
||||
const props = createDefaultProps({ onClickConfiguration: mockOnConfig })
|
||||
const { container } = render(<Header {...props} />)
|
||||
|
||||
@@ -402,7 +402,7 @@ describe('Header', () => {
|
||||
it('should not re-render when props remain the same', () => {
|
||||
// Arrange
|
||||
const props = createDefaultProps()
|
||||
const renderSpy = jest.fn()
|
||||
const renderSpy = vi.fn()
|
||||
|
||||
const TrackedHeader: React.FC<HeaderProps> = (trackedProps) => {
|
||||
renderSpy()
|
||||
@@ -573,7 +573,7 @@ describe('Header', () => {
|
||||
describe('Integration', () => {
|
||||
it('should work with full credential workflow', () => {
|
||||
// Arrange
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
onCredentialChange: mockOnCredentialChange,
|
||||
currentCredentialId: 'cred-1',
|
||||
@@ -597,7 +597,7 @@ describe('Header', () => {
|
||||
|
||||
it('should display all components together correctly', () => {
|
||||
// Arrange
|
||||
const mockOnConfig = jest.fn()
|
||||
const mockOnConfig = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
docTitle: 'Integration Test Docs',
|
||||
docLink: 'https://test.com/docs',
|
||||
|
||||
@@ -9,45 +9,54 @@ import { VarKindType } from '@/app/components/workflow/nodes/_base/types'
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock useDocLink - context hook requires mocking
|
||||
const mockDocLink = jest.fn((path?: string) => `https://docs.example.com${path || ''}`)
|
||||
jest.mock('@/context/i18n', () => ({
|
||||
const mockDocLink = vi.fn((path?: string) => `https://docs.example.com${path || ''}`)
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => mockDocLink,
|
||||
}))
|
||||
|
||||
// Mock dataset-detail context - context provider requires mocking
|
||||
let mockPipelineId = 'pipeline-123'
|
||||
jest.mock('@/context/dataset-detail', () => ({
|
||||
vi.mock('@/context/dataset-detail', () => ({
|
||||
useDatasetDetailContextWithSelector: (selector: (s: any) => any) => selector({ dataset: { pipeline_id: mockPipelineId } }),
|
||||
}))
|
||||
|
||||
// Mock modal context - context provider requires mocking
|
||||
const mockSetShowAccountSettingModal = jest.fn()
|
||||
jest.mock('@/context/modal-context', () => ({
|
||||
const mockSetShowAccountSettingModal = vi.fn()
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContextSelector: (selector: (s: any) => any) => selector({ setShowAccountSettingModal: mockSetShowAccountSettingModal }),
|
||||
}))
|
||||
|
||||
// Mock ssePost - API service requires mocking
|
||||
const mockSsePost = jest.fn()
|
||||
jest.mock('@/service/base', () => ({
|
||||
ssePost: (...args: any[]) => mockSsePost(...args),
|
||||
const { mockSsePost } = vi.hoisted(() => ({
|
||||
mockSsePost: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/base', () => ({
|
||||
ssePost: mockSsePost,
|
||||
}))
|
||||
|
||||
// Mock Toast.notify - static method that manipulates DOM, needs mocking to verify calls
|
||||
const mockToastNotify = jest.fn()
|
||||
jest.mock('@/app/components/base/toast', () => ({
|
||||
const { mockToastNotify } = vi.hoisted(() => ({
|
||||
mockToastNotify: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/toast', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
notify: (options: any) => mockToastNotify(options),
|
||||
notify: mockToastNotify,
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock useGetDataSourceAuth - API service hook requires mocking
|
||||
const mockUseGetDataSourceAuth = jest.fn()
|
||||
jest.mock('@/service/use-datasource', () => ({
|
||||
useGetDataSourceAuth: (params: any) => mockUseGetDataSourceAuth(params),
|
||||
const { mockUseGetDataSourceAuth } = vi.hoisted(() => ({
|
||||
mockUseGetDataSourceAuth: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-datasource', () => ({
|
||||
useGetDataSourceAuth: mockUseGetDataSourceAuth,
|
||||
}))
|
||||
|
||||
// Note: zustand/react/shallow useShallow is imported directly (simple utility function)
|
||||
@@ -58,24 +67,24 @@ const mockStoreState = {
|
||||
searchValue: '',
|
||||
selectedPagesId: new Set<string>(),
|
||||
currentCredentialId: '',
|
||||
setDocumentsData: jest.fn(),
|
||||
setSearchValue: jest.fn(),
|
||||
setSelectedPagesId: jest.fn(),
|
||||
setOnlineDocuments: jest.fn(),
|
||||
setCurrentDocument: jest.fn(),
|
||||
setDocumentsData: vi.fn(),
|
||||
setSearchValue: vi.fn(),
|
||||
setSelectedPagesId: vi.fn(),
|
||||
setOnlineDocuments: vi.fn(),
|
||||
setCurrentDocument: vi.fn(),
|
||||
}
|
||||
|
||||
const mockGetState = jest.fn(() => mockStoreState)
|
||||
const mockGetState = vi.fn(() => mockStoreState)
|
||||
const mockDataSourceStore = { getState: mockGetState }
|
||||
|
||||
jest.mock('../store', () => ({
|
||||
vi.mock('../store', () => ({
|
||||
useDataSourceStoreWithSelector: (selector: (s: any) => any) => selector(mockStoreState),
|
||||
useDataSourceStore: () => mockDataSourceStore,
|
||||
}))
|
||||
|
||||
// Mock Header component
|
||||
jest.mock('../base/header', () => {
|
||||
const MockHeader = (props: any) => (
|
||||
vi.mock('../base/header', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="header">
|
||||
<span data-testid="header-doc-title">{props.docTitle}</span>
|
||||
<span data-testid="header-doc-link">{props.docLink}</span>
|
||||
@@ -85,13 +94,12 @@ jest.mock('../base/header', () => {
|
||||
<button data-testid="header-credential-change" onClick={() => props.onCredentialChange('new-cred-id')}>Change Credential</button>
|
||||
<span data-testid="header-credentials-count">{props.credentials?.length || 0}</span>
|
||||
</div>
|
||||
)
|
||||
return MockHeader
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock SearchInput component
|
||||
jest.mock('@/app/components/base/notion-page-selector/search-input', () => {
|
||||
const MockSearchInput = ({ value, onChange }: { value: string; onChange: (v: string) => void }) => (
|
||||
vi.mock('@/app/components/base/notion-page-selector/search-input', () => ({
|
||||
default: ({ value, onChange }: { value: string; onChange: (v: string) => void }) => (
|
||||
<div data-testid="search-input">
|
||||
<input
|
||||
data-testid="search-input-field"
|
||||
@@ -100,13 +108,12 @@ jest.mock('@/app/components/base/notion-page-selector/search-input', () => {
|
||||
placeholder="Search"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
return MockSearchInput
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock PageSelector component
|
||||
jest.mock('./page-selector', () => {
|
||||
const MockPageSelector = (props: any) => (
|
||||
vi.mock('./page-selector', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="page-selector">
|
||||
<span data-testid="page-selector-checked-count">{props.checkedIds?.size || 0}</span>
|
||||
<span data-testid="page-selector-search-value">{props.searchValue}</span>
|
||||
@@ -126,27 +133,17 @@ jest.mock('./page-selector', () => {
|
||||
Preview Page
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
return MockPageSelector
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Title component
|
||||
jest.mock('./title', () => {
|
||||
const MockTitle = ({ name }: { name: string }) => (
|
||||
vi.mock('./title', () => ({
|
||||
default: ({ name }: { name: string }) => (
|
||||
<div data-testid="title">
|
||||
<span data-testid="title-name">{name}</span>
|
||||
</div>
|
||||
)
|
||||
return MockTitle
|
||||
})
|
||||
|
||||
// Mock Loading component
|
||||
jest.mock('@/app/components/base/loading', () => {
|
||||
const MockLoading = ({ type }: { type: string }) => (
|
||||
<div data-testid="loading" data-type={type}>Loading...</div>
|
||||
)
|
||||
return MockLoading
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// ==========================================
|
||||
// Test Data Builders
|
||||
@@ -197,7 +194,7 @@ type OnlineDocumentsProps = React.ComponentProps<typeof OnlineDocuments>
|
||||
const createDefaultProps = (overrides?: Partial<OnlineDocumentsProps>): OnlineDocumentsProps => ({
|
||||
nodeId: 'node-1',
|
||||
nodeData: createMockNodeData(),
|
||||
onCredentialChange: jest.fn(),
|
||||
onCredentialChange: vi.fn(),
|
||||
isInPipeline: false,
|
||||
supportBatchUpload: true,
|
||||
...overrides,
|
||||
@@ -208,18 +205,18 @@ const createDefaultProps = (overrides?: Partial<OnlineDocumentsProps>): OnlineDo
|
||||
// ==========================================
|
||||
describe('OnlineDocuments', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Reset store state
|
||||
mockStoreState.documentsData = []
|
||||
mockStoreState.searchValue = ''
|
||||
mockStoreState.selectedPagesId = new Set()
|
||||
mockStoreState.currentCredentialId = ''
|
||||
mockStoreState.setDocumentsData = jest.fn()
|
||||
mockStoreState.setSearchValue = jest.fn()
|
||||
mockStoreState.setSelectedPagesId = jest.fn()
|
||||
mockStoreState.setOnlineDocuments = jest.fn()
|
||||
mockStoreState.setCurrentDocument = jest.fn()
|
||||
mockStoreState.setDocumentsData = vi.fn()
|
||||
mockStoreState.setSearchValue = vi.fn()
|
||||
mockStoreState.setSelectedPagesId = vi.fn()
|
||||
mockStoreState.setOnlineDocuments = vi.fn()
|
||||
mockStoreState.setCurrentDocument = vi.fn()
|
||||
|
||||
// Reset context values
|
||||
mockPipelineId = 'pipeline-123'
|
||||
@@ -273,8 +270,7 @@ describe('OnlineDocuments', () => {
|
||||
render(<OnlineDocuments {...props} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('loading')).toHaveAttribute('data-type', 'app')
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render PageSelector when documentsData has content', () => {
|
||||
@@ -287,7 +283,7 @@ describe('OnlineDocuments', () => {
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('page-selector')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('loading')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('status')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Title with datasource_label', () => {
|
||||
@@ -493,7 +489,7 @@ describe('OnlineDocuments', () => {
|
||||
describe('onCredentialChange prop', () => {
|
||||
it('should pass onCredentialChange to Header', () => {
|
||||
// Arrange
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
|
||||
// Act
|
||||
@@ -761,7 +757,7 @@ describe('OnlineDocuments', () => {
|
||||
render(<OnlineDocuments {...props} />)
|
||||
|
||||
// Assert - Should show loading instead of PageSelector
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -831,7 +827,7 @@ describe('OnlineDocuments', () => {
|
||||
|
||||
it('should handle credential change', () => {
|
||||
// Arrange
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
render(<OnlineDocuments {...props} />)
|
||||
|
||||
@@ -1032,7 +1028,7 @@ describe('OnlineDocuments', () => {
|
||||
render(<OnlineDocuments {...props} />)
|
||||
|
||||
// Assert - Should show loading when documentsData is undefined
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle undefined datasource_parameters (line 79 branch)', () => {
|
||||
@@ -1219,7 +1215,7 @@ describe('OnlineDocuments', () => {
|
||||
const props: OnlineDocumentsProps = {
|
||||
nodeId: 'node-1',
|
||||
nodeData: createMockNodeData(),
|
||||
onCredentialChange: jest.fn(),
|
||||
onCredentialChange: vi.fn(),
|
||||
// isInPipeline and supportBatchUpload are not provided
|
||||
}
|
||||
|
||||
@@ -1303,13 +1299,13 @@ describe('OnlineDocuments', () => {
|
||||
})
|
||||
|
||||
// Should still show loading since documentsData is empty
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle credential change and refetch documents', () => {
|
||||
// Arrange
|
||||
mockStoreState.currentCredentialId = 'initial-cred'
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
|
||||
// Act
|
||||
@@ -1325,33 +1321,4 @@ describe('OnlineDocuments', () => {
|
||||
})
|
||||
|
||||
// ==========================================
|
||||
// Styling
|
||||
// ==========================================
|
||||
describe('Styling', () => {
|
||||
it('should apply correct container classes', () => {
|
||||
// Arrange
|
||||
const props = createDefaultProps()
|
||||
|
||||
// Act
|
||||
const { container } = render(<OnlineDocuments {...props} />)
|
||||
|
||||
// Assert
|
||||
const rootDiv = container.firstChild as HTMLElement
|
||||
expect(rootDiv).toHaveClass('flex', 'flex-col', 'gap-y-2')
|
||||
})
|
||||
|
||||
it('should apply correct classes to main content container', () => {
|
||||
// Arrange
|
||||
mockStoreState.documentsData = [createMockWorkspace()]
|
||||
const props = createDefaultProps()
|
||||
|
||||
// Act
|
||||
const { container } = render(<OnlineDocuments {...props} />)
|
||||
|
||||
// Assert
|
||||
const contentContainer = container.querySelector('.rounded-xl.border')
|
||||
expect(contentContainer).toBeInTheDocument()
|
||||
expect(contentContainer).toHaveClass('border-components-panel-border', 'bg-background-default-subtle')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,10 +9,10 @@ import { recursivePushInParentDescendants } from './utils'
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock react-window FixedSizeList - renders items directly for testing
|
||||
jest.mock('react-window', () => ({
|
||||
vi.mock('react-window', () => ({
|
||||
FixedSizeList: ({ children: ItemComponent, itemCount, itemData, itemKey }: any) => (
|
||||
<div data-testid="virtual-list">
|
||||
{Array.from({ length: itemCount }).map((_, index) => (
|
||||
@@ -25,6 +25,7 @@ jest.mock('react-window', () => ({
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
areEqual: (prevProps: any, nextProps: any) => prevProps === nextProps,
|
||||
}))
|
||||
|
||||
// Note: NotionIcon from @/app/components/base/ is NOT mocked - using real component per testing guidelines
|
||||
@@ -76,9 +77,9 @@ const createDefaultProps = (overrides?: Partial<PageSelectorProps>): PageSelecto
|
||||
searchValue: '',
|
||||
pagesMap: createMockPagesMap(defaultList),
|
||||
list: defaultList,
|
||||
onSelect: jest.fn(),
|
||||
onSelect: vi.fn(),
|
||||
canPreview: true,
|
||||
onPreview: jest.fn(),
|
||||
onPreview: vi.fn(),
|
||||
isMultipleChoice: true,
|
||||
currentCredentialId: 'cred-1',
|
||||
...overrides,
|
||||
@@ -103,7 +104,7 @@ const createHierarchicalPages = () => {
|
||||
// ==========================================
|
||||
describe('PageSelector', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// ==========================================
|
||||
@@ -539,7 +540,7 @@ describe('PageSelector', () => {
|
||||
describe('onSelect prop', () => {
|
||||
it('should call onSelect when checkbox is clicked', () => {
|
||||
// Arrange
|
||||
const mockOnSelect = jest.fn()
|
||||
const mockOnSelect = vi.fn()
|
||||
const props = createDefaultProps({ onSelect: mockOnSelect })
|
||||
|
||||
// Act
|
||||
@@ -553,7 +554,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should pass updated set to onSelect', () => {
|
||||
// Arrange
|
||||
const mockOnSelect = jest.fn()
|
||||
const mockOnSelect = vi.fn()
|
||||
const page = createMockPage({ page_id: 'page-1' })
|
||||
const props = createDefaultProps({
|
||||
list: [page],
|
||||
@@ -575,7 +576,7 @@ describe('PageSelector', () => {
|
||||
describe('onPreview prop', () => {
|
||||
it('should call onPreview when preview button is clicked', () => {
|
||||
// Arrange
|
||||
const mockOnPreview = jest.fn()
|
||||
const mockOnPreview = vi.fn()
|
||||
const page = createMockPage({ page_id: 'page-1' })
|
||||
const props = createDefaultProps({
|
||||
list: [page],
|
||||
@@ -679,7 +680,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should maintain currentPreviewPageId state', () => {
|
||||
// Arrange
|
||||
const mockOnPreview = jest.fn()
|
||||
const mockOnPreview = vi.fn()
|
||||
const pages = [
|
||||
createMockPage({ page_id: 'page-1', page_name: 'Page 1' }),
|
||||
createMockPage({ page_id: 'page-2', page_name: 'Page 2' }),
|
||||
@@ -833,7 +834,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should have stable handleCheck that adds page and descendants to selection', () => {
|
||||
// Arrange
|
||||
const mockOnSelect = jest.fn()
|
||||
const mockOnSelect = vi.fn()
|
||||
const { list, pagesMap } = createHierarchicalPages()
|
||||
const props = createDefaultProps({
|
||||
list,
|
||||
@@ -857,7 +858,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should have stable handleCheck that removes page and descendants from selection', () => {
|
||||
// Arrange
|
||||
const mockOnSelect = jest.fn()
|
||||
const mockOnSelect = vi.fn()
|
||||
const { list, pagesMap } = createHierarchicalPages()
|
||||
const props = createDefaultProps({
|
||||
list,
|
||||
@@ -879,7 +880,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should have stable handlePreview that updates currentPreviewPageId', () => {
|
||||
// Arrange
|
||||
const mockOnPreview = jest.fn()
|
||||
const mockOnPreview = vi.fn()
|
||||
const page = createMockPage({ page_id: 'preview-page' })
|
||||
const props = createDefaultProps({
|
||||
list: [page],
|
||||
@@ -1007,7 +1008,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should check/uncheck page when clicking checkbox', () => {
|
||||
// Arrange
|
||||
const mockOnSelect = jest.fn()
|
||||
const mockOnSelect = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
onSelect: mockOnSelect,
|
||||
checkedIds: new Set(),
|
||||
@@ -1023,7 +1024,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should select radio when clicking in single choice mode', () => {
|
||||
// Arrange
|
||||
const mockOnSelect = jest.fn()
|
||||
const mockOnSelect = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
onSelect: mockOnSelect,
|
||||
isMultipleChoice: false,
|
||||
@@ -1040,7 +1041,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should clear previous selection in single choice mode', () => {
|
||||
// Arrange
|
||||
const mockOnSelect = jest.fn()
|
||||
const mockOnSelect = vi.fn()
|
||||
const pages = [
|
||||
createMockPage({ page_id: 'page-1', page_name: 'Page 1' }),
|
||||
createMockPage({ page_id: 'page-2', page_name: 'Page 2' }),
|
||||
@@ -1067,7 +1068,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should trigger preview when clicking preview button', () => {
|
||||
// Arrange
|
||||
const mockOnPreview = jest.fn()
|
||||
const mockOnPreview = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
onPreview: mockOnPreview,
|
||||
canPreview: true,
|
||||
@@ -1083,7 +1084,7 @@ describe('PageSelector', () => {
|
||||
|
||||
it('should not cascade selection in search mode', () => {
|
||||
// Arrange
|
||||
const mockOnSelect = jest.fn()
|
||||
const mockOnSelect = vi.fn()
|
||||
const { list, pagesMap } = createHierarchicalPages()
|
||||
const props = createDefaultProps({
|
||||
list,
|
||||
@@ -1359,7 +1360,7 @@ describe('PageSelector', () => {
|
||||
searchValue: '',
|
||||
pagesMap: createMockPagesMap([createMockPage()]),
|
||||
list: [createMockPage()],
|
||||
onSelect: jest.fn(),
|
||||
onSelect: vi.fn(),
|
||||
currentCredentialId: 'cred-1',
|
||||
// canPreview defaults to true
|
||||
// isMultipleChoice defaults to true
|
||||
|
||||
@@ -6,11 +6,11 @@ import type { DataSourceNodeType } from '@/app/components/workflow/nodes/data-so
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock useToolIcon - hook has complex dependencies (API calls, stores)
|
||||
const mockUseToolIcon = jest.fn()
|
||||
jest.mock('@/app/components/workflow/hooks', () => ({
|
||||
const mockUseToolIcon = vi.fn()
|
||||
vi.mock('@/app/components/workflow/hooks', () => ({
|
||||
useToolIcon: (data: any) => mockUseToolIcon(data),
|
||||
}))
|
||||
|
||||
@@ -33,7 +33,7 @@ type ConnectProps = React.ComponentProps<typeof Connect>
|
||||
|
||||
const createDefaultProps = (overrides?: Partial<ConnectProps>): ConnectProps => ({
|
||||
nodeData: createMockNodeData(),
|
||||
onSetting: jest.fn(),
|
||||
onSetting: vi.fn(),
|
||||
...overrides,
|
||||
})
|
||||
|
||||
@@ -42,7 +42,7 @@ const createDefaultProps = (overrides?: Partial<ConnectProps>): ConnectProps =>
|
||||
// ==========================================
|
||||
describe('Connect', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Default mock return values
|
||||
mockUseToolIcon.mockReturnValue('https://example.com/icon.png')
|
||||
@@ -216,7 +216,7 @@ describe('Connect', () => {
|
||||
describe('onSetting prop', () => {
|
||||
it('should call onSetting when connect button is clicked', () => {
|
||||
// Arrange
|
||||
const mockOnSetting = jest.fn()
|
||||
const mockOnSetting = vi.fn()
|
||||
const props = createDefaultProps({ onSetting: mockOnSetting })
|
||||
|
||||
// Act
|
||||
@@ -229,7 +229,7 @@ describe('Connect', () => {
|
||||
|
||||
it('should call onSetting when button clicked', () => {
|
||||
// Arrange
|
||||
const mockOnSetting = jest.fn()
|
||||
const mockOnSetting = vi.fn()
|
||||
const props = createDefaultProps({ onSetting: mockOnSetting })
|
||||
|
||||
// Act
|
||||
@@ -243,7 +243,7 @@ describe('Connect', () => {
|
||||
|
||||
it('should call onSetting on each button click', () => {
|
||||
// Arrange
|
||||
const mockOnSetting = jest.fn()
|
||||
const mockOnSetting = vi.fn()
|
||||
const props = createDefaultProps({ onSetting: mockOnSetting })
|
||||
|
||||
// Act
|
||||
@@ -266,7 +266,7 @@ describe('Connect', () => {
|
||||
describe('Connect Button', () => {
|
||||
it('should trigger onSetting callback on click', () => {
|
||||
// Arrange
|
||||
const mockOnSetting = jest.fn()
|
||||
const mockOnSetting = vi.fn()
|
||||
const props = createDefaultProps({ onSetting: mockOnSetting })
|
||||
render(<Connect {...props} />)
|
||||
|
||||
@@ -291,7 +291,7 @@ describe('Connect', () => {
|
||||
|
||||
it('should handle keyboard interaction (Enter key)', () => {
|
||||
// Arrange
|
||||
const mockOnSetting = jest.fn()
|
||||
const mockOnSetting = vi.fn()
|
||||
const props = createDefaultProps({ onSetting: mockOnSetting })
|
||||
render(<Connect {...props} />)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import Dropdown from './index'
|
||||
|
||||
// ==========================================
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
// ==========================================
|
||||
|
||||
// ==========================================
|
||||
@@ -14,7 +14,7 @@ type DropdownProps = React.ComponentProps<typeof Dropdown>
|
||||
const createDefaultProps = (overrides?: Partial<DropdownProps>): DropdownProps => ({
|
||||
startIndex: 0,
|
||||
breadcrumbs: ['folder1', 'folder2'],
|
||||
onBreadcrumbClick: jest.fn(),
|
||||
onBreadcrumbClick: vi.fn(),
|
||||
...overrides,
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ const createDefaultProps = (overrides?: Partial<DropdownProps>): DropdownProps =
|
||||
// ==========================================
|
||||
describe('Dropdown', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// ==========================================
|
||||
@@ -115,7 +115,7 @@ describe('Dropdown', () => {
|
||||
describe('startIndex prop', () => {
|
||||
it('should pass startIndex to Menu component', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex: 5,
|
||||
breadcrumbs: ['folder1'],
|
||||
@@ -138,7 +138,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should calculate correct index for second item', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex: 3,
|
||||
breadcrumbs: ['folder1', 'folder2'],
|
||||
@@ -252,7 +252,7 @@ describe('Dropdown', () => {
|
||||
describe('onBreadcrumbClick prop', () => {
|
||||
it('should call onBreadcrumbClick with correct index when item clicked', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex: 0,
|
||||
breadcrumbs: ['folder1'],
|
||||
@@ -327,7 +327,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should close when breadcrumb item is clicked', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
breadcrumbs: ['test-folder'],
|
||||
onBreadcrumbClick: mockOnBreadcrumbClick,
|
||||
@@ -422,7 +422,7 @@ describe('Dropdown', () => {
|
||||
describe('handleBreadCrumbClick', () => {
|
||||
it('should call onBreadcrumbClick and close menu', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
breadcrumbs: ['folder1'],
|
||||
onBreadcrumbClick: mockOnBreadcrumbClick,
|
||||
@@ -450,7 +450,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should pass correct index to onBreadcrumbClick for each item', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex: 2,
|
||||
breadcrumbs: ['folder1', 'folder2', 'folder3'],
|
||||
@@ -484,7 +484,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should maintain stable callback after rerender with same props', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
breadcrumbs: ['folder'],
|
||||
onBreadcrumbClick: mockOnBreadcrumbClick,
|
||||
@@ -512,8 +512,8 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should update callback when onBreadcrumbClick prop changes', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick1 = jest.fn()
|
||||
const mockOnBreadcrumbClick2 = jest.fn()
|
||||
const mockOnBreadcrumbClick1 = vi.fn()
|
||||
const mockOnBreadcrumbClick2 = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
breadcrumbs: ['folder'],
|
||||
onBreadcrumbClick: mockOnBreadcrumbClick1,
|
||||
@@ -616,7 +616,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should handle startIndex of 0', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex: 0,
|
||||
breadcrumbs: ['folder'],
|
||||
@@ -637,7 +637,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should handle large startIndex values', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex: 999,
|
||||
breadcrumbs: ['folder'],
|
||||
@@ -700,7 +700,7 @@ describe('Dropdown', () => {
|
||||
{ startIndex: 10, breadcrumbs: ['a', 'b'], expectedIndex: 10 },
|
||||
])('should handle startIndex=$startIndex correctly', async ({ startIndex, breadcrumbs, expectedIndex }) => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex,
|
||||
breadcrumbs,
|
||||
@@ -764,7 +764,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should handle click on any menu item', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex: 0,
|
||||
breadcrumbs: ['first', 'second', 'third'],
|
||||
@@ -785,7 +785,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should close menu after any item click', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
breadcrumbs: ['item1', 'item2', 'item3'],
|
||||
onBreadcrumbClick: mockOnBreadcrumbClick,
|
||||
@@ -809,7 +809,7 @@ describe('Dropdown', () => {
|
||||
|
||||
it('should correctly calculate index for each item based on startIndex', async () => {
|
||||
// Arrange
|
||||
const mockOnBreadcrumbClick = jest.fn()
|
||||
const mockOnBreadcrumbClick = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
startIndex: 3,
|
||||
breadcrumbs: ['folder-a', 'folder-b', 'folder-c'],
|
||||
|
||||
@@ -6,24 +6,24 @@ import Breadcrumbs from './index'
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock store - context provider requires mocking
|
||||
const mockStoreState = {
|
||||
hasBucket: false,
|
||||
breadcrumbs: [] as string[],
|
||||
prefix: [] as string[],
|
||||
setOnlineDriveFileList: jest.fn(),
|
||||
setSelectedFileIds: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
setPrefix: jest.fn(),
|
||||
setBucket: jest.fn(),
|
||||
setOnlineDriveFileList: vi.fn(),
|
||||
setSelectedFileIds: vi.fn(),
|
||||
setBreadcrumbs: vi.fn(),
|
||||
setPrefix: vi.fn(),
|
||||
setBucket: vi.fn(),
|
||||
}
|
||||
|
||||
const mockGetState = jest.fn(() => mockStoreState)
|
||||
const mockGetState = vi.fn(() => mockStoreState)
|
||||
const mockDataSourceStore = { getState: mockGetState }
|
||||
|
||||
jest.mock('../../../../store', () => ({
|
||||
vi.mock('../../../../store', () => ({
|
||||
useDataSourceStore: () => mockDataSourceStore,
|
||||
useDataSourceStoreWithSelector: (selector: (s: typeof mockStoreState) => unknown) => selector(mockStoreState),
|
||||
}))
|
||||
@@ -49,11 +49,11 @@ const resetMockStoreState = () => {
|
||||
mockStoreState.hasBucket = false
|
||||
mockStoreState.breadcrumbs = []
|
||||
mockStoreState.prefix = []
|
||||
mockStoreState.setOnlineDriveFileList = jest.fn()
|
||||
mockStoreState.setSelectedFileIds = jest.fn()
|
||||
mockStoreState.setBreadcrumbs = jest.fn()
|
||||
mockStoreState.setPrefix = jest.fn()
|
||||
mockStoreState.setBucket = jest.fn()
|
||||
mockStoreState.setOnlineDriveFileList = vi.fn()
|
||||
mockStoreState.setSelectedFileIds = vi.fn()
|
||||
mockStoreState.setBreadcrumbs = vi.fn()
|
||||
mockStoreState.setPrefix = vi.fn()
|
||||
mockStoreState.setBucket = vi.fn()
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
@@ -61,7 +61,7 @@ const resetMockStoreState = () => {
|
||||
// ==========================================
|
||||
describe('Breadcrumbs', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
resetMockStoreState()
|
||||
})
|
||||
|
||||
|
||||
@@ -6,24 +6,24 @@ import Header from './index'
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock store - required by Breadcrumbs component
|
||||
const mockStoreState = {
|
||||
hasBucket: false,
|
||||
setOnlineDriveFileList: jest.fn(),
|
||||
setSelectedFileIds: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
setPrefix: jest.fn(),
|
||||
setBucket: jest.fn(),
|
||||
setOnlineDriveFileList: vi.fn(),
|
||||
setSelectedFileIds: vi.fn(),
|
||||
setBreadcrumbs: vi.fn(),
|
||||
setPrefix: vi.fn(),
|
||||
setBucket: vi.fn(),
|
||||
breadcrumbs: [],
|
||||
prefix: [],
|
||||
}
|
||||
|
||||
const mockGetState = jest.fn(() => mockStoreState)
|
||||
const mockGetState = vi.fn(() => mockStoreState)
|
||||
const mockDataSourceStore = { getState: mockGetState }
|
||||
|
||||
jest.mock('../../../store', () => ({
|
||||
vi.mock('../../../store', () => ({
|
||||
useDataSourceStore: () => mockDataSourceStore,
|
||||
useDataSourceStoreWithSelector: (selector: (s: typeof mockStoreState) => unknown) => selector(mockStoreState),
|
||||
}))
|
||||
@@ -39,8 +39,8 @@ const createDefaultProps = (overrides?: Partial<HeaderProps>): HeaderProps => ({
|
||||
keywords: '',
|
||||
bucket: '',
|
||||
searchResultsLength: 0,
|
||||
handleInputChange: jest.fn(),
|
||||
handleResetKeywords: jest.fn(),
|
||||
handleInputChange: vi.fn(),
|
||||
handleResetKeywords: vi.fn(),
|
||||
isInPipeline: false,
|
||||
...overrides,
|
||||
})
|
||||
@@ -50,11 +50,11 @@ const createDefaultProps = (overrides?: Partial<HeaderProps>): HeaderProps => ({
|
||||
// ==========================================
|
||||
const resetMockStoreState = () => {
|
||||
mockStoreState.hasBucket = false
|
||||
mockStoreState.setOnlineDriveFileList = jest.fn()
|
||||
mockStoreState.setSelectedFileIds = jest.fn()
|
||||
mockStoreState.setBreadcrumbs = jest.fn()
|
||||
mockStoreState.setPrefix = jest.fn()
|
||||
mockStoreState.setBucket = jest.fn()
|
||||
mockStoreState.setOnlineDriveFileList = vi.fn()
|
||||
mockStoreState.setSelectedFileIds = vi.fn()
|
||||
mockStoreState.setBreadcrumbs = vi.fn()
|
||||
mockStoreState.setPrefix = vi.fn()
|
||||
mockStoreState.setBucket = vi.fn()
|
||||
mockStoreState.breadcrumbs = []
|
||||
mockStoreState.prefix = []
|
||||
}
|
||||
@@ -64,7 +64,7 @@ const resetMockStoreState = () => {
|
||||
// ==========================================
|
||||
describe('Header', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
resetMockStoreState()
|
||||
})
|
||||
|
||||
@@ -333,7 +333,7 @@ describe('Header', () => {
|
||||
describe('handleInputChange', () => {
|
||||
it('should call handleInputChange when input value changes', () => {
|
||||
// Arrange
|
||||
const mockHandleInputChange = jest.fn()
|
||||
const mockHandleInputChange = vi.fn()
|
||||
const props = createDefaultProps({ handleInputChange: mockHandleInputChange })
|
||||
render(<Header {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -349,7 +349,7 @@ describe('Header', () => {
|
||||
|
||||
it('should call handleInputChange on each keystroke', () => {
|
||||
// Arrange
|
||||
const mockHandleInputChange = jest.fn()
|
||||
const mockHandleInputChange = vi.fn()
|
||||
const props = createDefaultProps({ handleInputChange: mockHandleInputChange })
|
||||
render(<Header {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -365,7 +365,7 @@ describe('Header', () => {
|
||||
|
||||
it('should handle empty string input', () => {
|
||||
// Arrange
|
||||
const mockHandleInputChange = jest.fn()
|
||||
const mockHandleInputChange = vi.fn()
|
||||
const props = createDefaultProps({ inputValue: 'existing', handleInputChange: mockHandleInputChange })
|
||||
render(<Header {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -380,7 +380,7 @@ describe('Header', () => {
|
||||
|
||||
it('should handle whitespace-only input', () => {
|
||||
// Arrange
|
||||
const mockHandleInputChange = jest.fn()
|
||||
const mockHandleInputChange = vi.fn()
|
||||
const props = createDefaultProps({ handleInputChange: mockHandleInputChange })
|
||||
render(<Header {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -397,7 +397,7 @@ describe('Header', () => {
|
||||
describe('handleResetKeywords', () => {
|
||||
it('should call handleResetKeywords when clear icon is clicked', () => {
|
||||
// Arrange
|
||||
const mockHandleResetKeywords = jest.fn()
|
||||
const mockHandleResetKeywords = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
inputValue: 'to-clear',
|
||||
handleResetKeywords: mockHandleResetKeywords,
|
||||
@@ -446,8 +446,8 @@ describe('Header', () => {
|
||||
|
||||
it('should not re-render when props are the same', () => {
|
||||
// Arrange
|
||||
const mockHandleInputChange = jest.fn()
|
||||
const mockHandleResetKeywords = jest.fn()
|
||||
const mockHandleInputChange = vi.fn()
|
||||
const mockHandleResetKeywords = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
handleInputChange: mockHandleInputChange,
|
||||
handleResetKeywords: mockHandleResetKeywords,
|
||||
@@ -571,7 +571,7 @@ describe('Header', () => {
|
||||
|
||||
it('should pass the event object to handleInputChange callback', () => {
|
||||
// Arrange
|
||||
const mockHandleInputChange = jest.fn()
|
||||
const mockHandleInputChange = vi.fn()
|
||||
const props = createDefaultProps({ handleInputChange: mockHandleInputChange })
|
||||
render(<Header {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -664,8 +664,8 @@ describe('Header', () => {
|
||||
|
||||
it('should pass correct props to Input component', () => {
|
||||
// Arrange
|
||||
const mockHandleInputChange = jest.fn()
|
||||
const mockHandleResetKeywords = jest.fn()
|
||||
const mockHandleInputChange = vi.fn()
|
||||
const mockHandleResetKeywords = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
inputValue: 'test-input',
|
||||
handleInputChange: mockHandleInputChange,
|
||||
@@ -691,7 +691,7 @@ describe('Header', () => {
|
||||
describe('Callback Stability', () => {
|
||||
it('should maintain stable handleInputChange callback after rerender', () => {
|
||||
// Arrange
|
||||
const mockHandleInputChange = jest.fn()
|
||||
const mockHandleInputChange = vi.fn()
|
||||
const props = createDefaultProps({ handleInputChange: mockHandleInputChange })
|
||||
const { rerender } = render(<Header {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -707,7 +707,7 @@ describe('Header', () => {
|
||||
|
||||
it('should maintain stable handleResetKeywords callback after rerender', () => {
|
||||
// Arrange
|
||||
const mockHandleResetKeywords = jest.fn()
|
||||
const mockHandleResetKeywords = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
inputValue: 'to-clear',
|
||||
handleResetKeywords: mockHandleResetKeywords,
|
||||
|
||||
@@ -8,11 +8,11 @@ import { OnlineDriveFileType } from '@/models/pipeline'
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock ahooks useDebounceFn - third-party library requires mocking
|
||||
const mockDebounceFnRun = jest.fn()
|
||||
jest.mock('ahooks', () => ({
|
||||
const mockDebounceFnRun = vi.fn()
|
||||
vi.mock('ahooks', () => ({
|
||||
useDebounceFn: (fn: (...args: any[]) => void) => {
|
||||
mockDebounceFnRun.mockImplementation(fn)
|
||||
return { run: mockDebounceFnRun }
|
||||
@@ -21,21 +21,21 @@ jest.mock('ahooks', () => ({
|
||||
|
||||
// Mock store - context provider requires mocking
|
||||
const mockStoreState = {
|
||||
setNextPageParameters: jest.fn(),
|
||||
setNextPageParameters: vi.fn(),
|
||||
currentNextPageParametersRef: { current: {} },
|
||||
isTruncated: { current: false },
|
||||
hasBucket: false,
|
||||
setOnlineDriveFileList: jest.fn(),
|
||||
setSelectedFileIds: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
setPrefix: jest.fn(),
|
||||
setBucket: jest.fn(),
|
||||
setOnlineDriveFileList: vi.fn(),
|
||||
setSelectedFileIds: vi.fn(),
|
||||
setBreadcrumbs: vi.fn(),
|
||||
setPrefix: vi.fn(),
|
||||
setBucket: vi.fn(),
|
||||
}
|
||||
|
||||
const mockGetState = jest.fn(() => mockStoreState)
|
||||
const mockGetState = vi.fn(() => mockStoreState)
|
||||
const mockDataSourceStore = { getState: mockGetState }
|
||||
|
||||
jest.mock('../../store', () => ({
|
||||
vi.mock('../../store', () => ({
|
||||
useDataSourceStore: () => mockDataSourceStore,
|
||||
useDataSourceStoreWithSelector: (selector: (s: any) => any) => selector(mockStoreState),
|
||||
}))
|
||||
@@ -60,11 +60,11 @@ const createDefaultProps = (overrides?: Partial<FileListProps>): FileListProps =
|
||||
keywords: '',
|
||||
bucket: '',
|
||||
isInPipeline: false,
|
||||
resetKeywords: jest.fn(),
|
||||
updateKeywords: jest.fn(),
|
||||
resetKeywords: vi.fn(),
|
||||
updateKeywords: vi.fn(),
|
||||
searchResultsLength: 0,
|
||||
handleSelectFile: jest.fn(),
|
||||
handleOpenFolder: jest.fn(),
|
||||
handleSelectFile: vi.fn(),
|
||||
handleOpenFolder: vi.fn(),
|
||||
isLoading: false,
|
||||
supportBatchUpload: true,
|
||||
...overrides,
|
||||
@@ -74,15 +74,15 @@ const createDefaultProps = (overrides?: Partial<FileListProps>): FileListProps =
|
||||
// Helper Functions
|
||||
// ==========================================
|
||||
const resetMockStoreState = () => {
|
||||
mockStoreState.setNextPageParameters = jest.fn()
|
||||
mockStoreState.setNextPageParameters = vi.fn()
|
||||
mockStoreState.currentNextPageParametersRef = { current: {} }
|
||||
mockStoreState.isTruncated = { current: false }
|
||||
mockStoreState.hasBucket = false
|
||||
mockStoreState.setOnlineDriveFileList = jest.fn()
|
||||
mockStoreState.setSelectedFileIds = jest.fn()
|
||||
mockStoreState.setBreadcrumbs = jest.fn()
|
||||
mockStoreState.setPrefix = jest.fn()
|
||||
mockStoreState.setBucket = jest.fn()
|
||||
mockStoreState.setOnlineDriveFileList = vi.fn()
|
||||
mockStoreState.setSelectedFileIds = vi.fn()
|
||||
mockStoreState.setBreadcrumbs = vi.fn()
|
||||
mockStoreState.setPrefix = vi.fn()
|
||||
mockStoreState.setBucket = vi.fn()
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
@@ -90,7 +90,7 @@ const resetMockStoreState = () => {
|
||||
// ==========================================
|
||||
describe('FileList', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
resetMockStoreState()
|
||||
mockDebounceFnRun.mockClear()
|
||||
})
|
||||
@@ -345,7 +345,7 @@ describe('FileList', () => {
|
||||
describe('debounced keywords update', () => {
|
||||
it('should call updateKeywords with debounce when input changes', () => {
|
||||
// Arrange
|
||||
const mockUpdateKeywords = jest.fn()
|
||||
const mockUpdateKeywords = vi.fn()
|
||||
const props = createDefaultProps({ updateKeywords: mockUpdateKeywords })
|
||||
render(<FileList {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -379,7 +379,7 @@ describe('FileList', () => {
|
||||
|
||||
it('should trigger debounced updateKeywords on input change', () => {
|
||||
// Arrange
|
||||
const mockUpdateKeywords = jest.fn()
|
||||
const mockUpdateKeywords = vi.fn()
|
||||
const props = createDefaultProps({ updateKeywords: mockUpdateKeywords })
|
||||
render(<FileList {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -393,7 +393,7 @@ describe('FileList', () => {
|
||||
|
||||
it('should handle multiple sequential input changes', () => {
|
||||
// Arrange
|
||||
const mockUpdateKeywords = jest.fn()
|
||||
const mockUpdateKeywords = vi.fn()
|
||||
const props = createDefaultProps({ updateKeywords: mockUpdateKeywords })
|
||||
render(<FileList {...props} />)
|
||||
const input = screen.getByPlaceholderText('datasetPipeline.onlineDrive.breadcrumbs.searchPlaceholder')
|
||||
@@ -413,7 +413,7 @@ describe('FileList', () => {
|
||||
describe('handleResetKeywords', () => {
|
||||
it('should call resetKeywords prop when clear button is clicked', () => {
|
||||
// Arrange
|
||||
const mockResetKeywords = jest.fn()
|
||||
const mockResetKeywords = vi.fn()
|
||||
const props = createDefaultProps({ resetKeywords: mockResetKeywords, keywords: 'to-reset' })
|
||||
const { container } = render(<FileList {...props} />)
|
||||
|
||||
@@ -446,7 +446,7 @@ describe('FileList', () => {
|
||||
describe('handleSelectFile', () => {
|
||||
it('should call handleSelectFile when file item is clicked', () => {
|
||||
// Arrange
|
||||
const mockHandleSelectFile = jest.fn()
|
||||
const mockHandleSelectFile = vi.fn()
|
||||
const fileList = [createMockOnlineDriveFile({ id: 'file-1', name: 'test.txt' })]
|
||||
const props = createDefaultProps({ handleSelectFile: mockHandleSelectFile, fileList })
|
||||
render(<FileList {...props} />)
|
||||
@@ -467,7 +467,7 @@ describe('FileList', () => {
|
||||
describe('handleOpenFolder', () => {
|
||||
it('should call handleOpenFolder when folder item is clicked', () => {
|
||||
// Arrange
|
||||
const mockHandleOpenFolder = jest.fn()
|
||||
const mockHandleOpenFolder = vi.fn()
|
||||
const fileList = [createMockOnlineDriveFile({ id: 'folder-1', name: 'my-folder', type: OnlineDriveFileType.folder })]
|
||||
const props = createDefaultProps({ handleOpenFolder: mockHandleOpenFolder, fileList })
|
||||
render(<FileList {...props} />)
|
||||
@@ -714,7 +714,7 @@ describe('FileList', () => {
|
||||
describe('Callback Stability', () => {
|
||||
it('should maintain stable handleSelectFile callback', () => {
|
||||
// Arrange
|
||||
const mockHandleSelectFile = jest.fn()
|
||||
const mockHandleSelectFile = vi.fn()
|
||||
const fileList = [createMockOnlineDriveFile({ id: 'file-1', name: 'test.txt' })]
|
||||
const props = createDefaultProps({ handleSelectFile: mockHandleSelectFile, fileList })
|
||||
const { rerender } = render(<FileList {...props} />)
|
||||
@@ -735,7 +735,7 @@ describe('FileList', () => {
|
||||
|
||||
it('should maintain stable handleOpenFolder callback', () => {
|
||||
// Arrange
|
||||
const mockHandleOpenFolder = jest.fn()
|
||||
const mockHandleOpenFolder = vi.fn()
|
||||
const fileList = [createMockOnlineDriveFile({ id: 'folder-1', name: 'my-folder', type: OnlineDriveFileType.folder })]
|
||||
const props = createDefaultProps({ handleOpenFolder: mockHandleOpenFolder, fileList })
|
||||
const { rerender } = render(<FileList {...props} />)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Mock } from 'vitest'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import List from './index'
|
||||
@@ -8,19 +9,11 @@ import { OnlineDriveFileType } from '@/models/pipeline'
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
|
||||
// Mock Loading component - base component with simple render
|
||||
jest.mock('@/app/components/base/loading', () => {
|
||||
const MockLoading = ({ type }: { type?: string }) => (
|
||||
<div data-testid="loading" data-type={type}>Loading...</div>
|
||||
)
|
||||
return MockLoading
|
||||
})
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock Item component for List tests - child component with complex behavior
|
||||
jest.mock('./item', () => {
|
||||
const MockItem = ({ file, isSelected, onSelect, onOpen, isMultipleChoice }: {
|
||||
vi.mock('./item', () => ({
|
||||
default: ({ file, isSelected, onSelect, onOpen, isMultipleChoice }: {
|
||||
file: OnlineDriveFile
|
||||
isSelected: boolean
|
||||
onSelect: (file: OnlineDriveFile) => void
|
||||
@@ -38,33 +31,30 @@ jest.mock('./item', () => {
|
||||
<button data-testid={`item-open-${file.id}`} onClick={() => onOpen(file)}>Open</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return MockItem
|
||||
})
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock EmptyFolder component for List tests
|
||||
jest.mock('./empty-folder', () => {
|
||||
const MockEmptyFolder = () => (
|
||||
vi.mock('./empty-folder', () => ({
|
||||
default: () => (
|
||||
<div data-testid="empty-folder">Empty Folder</div>
|
||||
)
|
||||
return MockEmptyFolder
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock EmptySearchResult component for List tests
|
||||
jest.mock('./empty-search-result', () => {
|
||||
const MockEmptySearchResult = ({ onResetKeywords }: { onResetKeywords: () => void }) => (
|
||||
vi.mock('./empty-search-result', () => ({
|
||||
default: ({ onResetKeywords }: { onResetKeywords: () => void }) => (
|
||||
<div data-testid="empty-search-result">
|
||||
<span>No results</span>
|
||||
<button data-testid="reset-keywords-btn" onClick={onResetKeywords}>Reset</button>
|
||||
</div>
|
||||
)
|
||||
return MockEmptySearchResult
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock store state and refs
|
||||
const mockIsTruncated = { current: false }
|
||||
const mockCurrentNextPageParametersRef = { current: {} as Record<string, any> }
|
||||
const mockSetNextPageParameters = jest.fn()
|
||||
const mockSetNextPageParameters = vi.fn()
|
||||
|
||||
const mockStoreState = {
|
||||
isTruncated: mockIsTruncated,
|
||||
@@ -72,10 +62,10 @@ const mockStoreState = {
|
||||
setNextPageParameters: mockSetNextPageParameters,
|
||||
}
|
||||
|
||||
const mockGetState = jest.fn(() => mockStoreState)
|
||||
const mockGetState = vi.fn(() => mockStoreState)
|
||||
const mockDataSourceStore = { getState: mockGetState }
|
||||
|
||||
jest.mock('../../../store', () => ({
|
||||
vi.mock('../../../store', () => ({
|
||||
useDataSourceStore: () => mockDataSourceStore,
|
||||
}))
|
||||
|
||||
@@ -106,9 +96,9 @@ const createDefaultProps = (overrides?: Partial<ListProps>): ListProps => ({
|
||||
keywords: '',
|
||||
isLoading: false,
|
||||
supportBatchUpload: true,
|
||||
handleResetKeywords: jest.fn(),
|
||||
handleSelectFile: jest.fn(),
|
||||
handleOpenFolder: jest.fn(),
|
||||
handleResetKeywords: vi.fn(),
|
||||
handleSelectFile: vi.fn(),
|
||||
handleOpenFolder: vi.fn(),
|
||||
...overrides,
|
||||
})
|
||||
|
||||
@@ -117,16 +107,16 @@ const createDefaultProps = (overrides?: Partial<ListProps>): ListProps => ({
|
||||
// ==========================================
|
||||
let mockIntersectionObserverCallback: IntersectionObserverCallback | null = null
|
||||
let mockIntersectionObserverInstance: {
|
||||
observe: jest.Mock
|
||||
disconnect: jest.Mock
|
||||
unobserve: jest.Mock
|
||||
observe: Mock
|
||||
disconnect: Mock
|
||||
unobserve: Mock
|
||||
} | null = null
|
||||
|
||||
const createMockIntersectionObserver = () => {
|
||||
const instance = {
|
||||
observe: jest.fn(),
|
||||
disconnect: jest.fn(),
|
||||
unobserve: jest.fn(),
|
||||
observe: vi.fn(),
|
||||
disconnect: vi.fn(),
|
||||
unobserve: vi.fn(),
|
||||
}
|
||||
mockIntersectionObserverInstance = instance
|
||||
|
||||
@@ -178,7 +168,7 @@ describe('List', () => {
|
||||
const originalIntersectionObserver = window.IntersectionObserver
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
resetMockStoreState()
|
||||
mockIntersectionObserverCallback = null
|
||||
mockIntersectionObserverInstance = null
|
||||
@@ -218,8 +208,7 @@ describe('List', () => {
|
||||
render(<List {...props} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('loading')).toHaveAttribute('data-type', 'app')
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render EmptyFolder when folder is empty and not loading', () => {
|
||||
@@ -274,40 +263,12 @@ describe('List', () => {
|
||||
isLoading: true,
|
||||
})
|
||||
|
||||
// Act
|
||||
const { container } = render(<List {...props} />)
|
||||
|
||||
// Assert - Should show files AND loading spinner (animation-spin class)
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
expect(container.querySelector('.animation-spin')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render Loading component when partial loading', () => {
|
||||
// Arrange
|
||||
const fileList = createMockFileList(2)
|
||||
const props = createDefaultProps({
|
||||
fileList,
|
||||
isLoading: true,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<List {...props} />)
|
||||
|
||||
// Assert - Full page loading should not appear
|
||||
expect(screen.queryByTestId('loading')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render anchor div for infinite scroll', () => {
|
||||
// Arrange
|
||||
const fileList = createMockFileList(2)
|
||||
const props = createDefaultProps({ fileList })
|
||||
|
||||
// Act
|
||||
const { container } = render(<List {...props} />)
|
||||
|
||||
// Assert - Anchor div should exist with h-0 class
|
||||
const anchorDiv = container.querySelector('.h-0')
|
||||
expect(anchorDiv).toBeInTheDocument()
|
||||
// Assert - Should show files AND loading indicator
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -462,15 +423,16 @@ describe('List', () => {
|
||||
const props = createDefaultProps({ isLoading, fileList })
|
||||
|
||||
// Act
|
||||
const { container } = render(<List {...props} />)
|
||||
render(<List {...props} />)
|
||||
|
||||
// Assert
|
||||
switch (expected) {
|
||||
case 'isAllLoading':
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
break
|
||||
case 'isPartialLoading':
|
||||
expect(container.querySelector('.animation-spin')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
break
|
||||
case 'isEmpty':
|
||||
expect(screen.getByTestId('empty-folder')).toBeInTheDocument()
|
||||
@@ -522,7 +484,7 @@ describe('List', () => {
|
||||
describe('File Selection', () => {
|
||||
it('should call handleSelectFile when selecting a file', () => {
|
||||
// Arrange
|
||||
const handleSelectFile = jest.fn()
|
||||
const handleSelectFile = vi.fn()
|
||||
const fileList = createMockFileList(2)
|
||||
const props = createDefaultProps({
|
||||
fileList,
|
||||
@@ -539,7 +501,7 @@ describe('List', () => {
|
||||
|
||||
it('should call handleSelectFile with correct file data', () => {
|
||||
// Arrange
|
||||
const handleSelectFile = jest.fn()
|
||||
const handleSelectFile = vi.fn()
|
||||
const fileList = [
|
||||
createMockOnlineDriveFile({ id: 'unique-id', name: 'special-file.pdf', size: 5000 }),
|
||||
]
|
||||
@@ -566,7 +528,7 @@ describe('List', () => {
|
||||
describe('Folder Navigation', () => {
|
||||
it('should call handleOpenFolder when opening a folder', () => {
|
||||
// Arrange
|
||||
const handleOpenFolder = jest.fn()
|
||||
const handleOpenFolder = vi.fn()
|
||||
const fileList = [
|
||||
createMockOnlineDriveFile({ id: 'folder-1', name: 'Documents', type: OnlineDriveFileType.folder }),
|
||||
]
|
||||
@@ -587,7 +549,7 @@ describe('List', () => {
|
||||
describe('Reset Keywords', () => {
|
||||
it('should call handleResetKeywords when reset button is clicked', () => {
|
||||
// Arrange
|
||||
const handleResetKeywords = jest.fn()
|
||||
const handleResetKeywords = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
fileList: [],
|
||||
keywords: 'search-term',
|
||||
@@ -639,12 +601,13 @@ describe('List', () => {
|
||||
const props = createDefaultProps({ fileList })
|
||||
|
||||
// Act
|
||||
const { container } = render(<List {...props} />)
|
||||
render(<List {...props} />)
|
||||
|
||||
// Assert
|
||||
expect(mockIntersectionObserverInstance?.observe).toHaveBeenCalled()
|
||||
const anchorDiv = container.querySelector('.h-0')
|
||||
expect(anchorDiv).toBeInTheDocument()
|
||||
const observedElement = mockIntersectionObserverInstance?.observe.mock.calls[0]?.[0]
|
||||
expect(observedElement).toBeInstanceOf(HTMLElement)
|
||||
expect(observedElement as HTMLElement).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -769,7 +732,7 @@ describe('List', () => {
|
||||
// Arrange
|
||||
const fileList = createMockFileList(2)
|
||||
const props = createDefaultProps({ fileList })
|
||||
const renderSpy = jest.fn()
|
||||
const renderSpy = vi.fn()
|
||||
|
||||
// Create a wrapper component to track renders
|
||||
const TestWrapper = ({ testProps }: { testProps: ListProps }) => {
|
||||
@@ -832,16 +795,16 @@ describe('List', () => {
|
||||
const props1 = createDefaultProps({ fileList, isLoading: false })
|
||||
const props2 = createDefaultProps({ fileList, isLoading: true })
|
||||
|
||||
const { rerender, container } = render(<List {...props1} />)
|
||||
const { rerender } = render(<List {...props1} />)
|
||||
|
||||
// Assert initial state - no loading spinner
|
||||
expect(container.querySelector('.animation-spin')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('status')).not.toBeInTheDocument()
|
||||
|
||||
// Act
|
||||
rerender(<List {...props2} />)
|
||||
|
||||
// Assert - loading spinner should appear
|
||||
expect(container.querySelector('.animation-spin')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1003,13 +966,13 @@ describe('List', () => {
|
||||
const { rerender } = render(<List {...props1} />)
|
||||
|
||||
// Assert initial loading state
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
|
||||
// Act
|
||||
rerender(<List {...props2} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByTestId('loading')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('status')).not.toBeInTheDocument()
|
||||
expect(screen.getByTestId('empty-folder')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -1022,13 +985,13 @@ describe('List', () => {
|
||||
const { rerender } = render(<List {...props1} />)
|
||||
|
||||
// Assert initial loading state
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
|
||||
// Act
|
||||
rerender(<List {...props2} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByTestId('loading')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('status')).not.toBeInTheDocument()
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@@ -1038,16 +1001,16 @@ describe('List', () => {
|
||||
const props1 = createDefaultProps({ isLoading: true, fileList })
|
||||
const props2 = createDefaultProps({ isLoading: false, fileList })
|
||||
|
||||
const { rerender, container } = render(<List {...props1} />)
|
||||
const { rerender } = render(<List {...props1} />)
|
||||
|
||||
// Assert initial partial loading state
|
||||
expect(container.querySelector('.animation-spin')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
|
||||
// Act
|
||||
rerender(<List {...props2} />)
|
||||
|
||||
// Assert
|
||||
expect(container.querySelector('.animation-spin')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('status')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1130,15 +1093,16 @@ describe('List', () => {
|
||||
const props = createDefaultProps({ fileList, isLoading, keywords })
|
||||
|
||||
// Act
|
||||
const { container } = render(<List {...props} />)
|
||||
render(<List {...props} />)
|
||||
|
||||
// Assert
|
||||
switch (expectedState) {
|
||||
case 'all-loading':
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
break
|
||||
case 'partial-loading':
|
||||
expect(container.querySelector('.animation-spin')).toBeInTheDocument()
|
||||
expect(screen.getByRole('status')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('item-file-1')).toBeInTheDocument()
|
||||
break
|
||||
case 'empty-folder':
|
||||
expect(screen.getByTestId('empty-folder')).toBeInTheDocument()
|
||||
@@ -1179,22 +1143,9 @@ describe('List', () => {
|
||||
// Accessibility Tests
|
||||
// ==========================================
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper container structure', () => {
|
||||
// Arrange
|
||||
const fileList = createMockFileList(2)
|
||||
const props = createDefaultProps({ fileList })
|
||||
|
||||
// Act
|
||||
const { container } = render(<List {...props} />)
|
||||
|
||||
// Assert - Container should be scrollable
|
||||
const scrollContainer = container.querySelector('.overflow-y-auto')
|
||||
expect(scrollContainer).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should allow interaction with reset keywords button in empty search state', () => {
|
||||
// Arrange
|
||||
const handleResetKeywords = jest.fn()
|
||||
const handleResetKeywords = vi.fn()
|
||||
const props = createDefaultProps({
|
||||
fileList: [],
|
||||
keywords: 'search-term',
|
||||
@@ -1218,10 +1169,15 @@ describe('List', () => {
|
||||
// ==========================================
|
||||
describe('EmptyFolder', () => {
|
||||
// Get real component for testing
|
||||
const ActualEmptyFolder = jest.requireActual('./empty-folder').default
|
||||
let ActualEmptyFolder: React.ComponentType
|
||||
|
||||
beforeAll(async () => {
|
||||
const mod = await vi.importActual<{ default: React.ComponentType }>('./empty-folder')
|
||||
ActualEmptyFolder = mod.default
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -1234,18 +1190,6 @@ describe('EmptyFolder', () => {
|
||||
render(<ActualEmptyFolder />)
|
||||
expect(screen.getByText(/datasetPipeline\.onlineDrive\.emptyFolder/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with correct container classes', () => {
|
||||
const { container } = render(<ActualEmptyFolder />)
|
||||
const wrapper = container.firstChild as HTMLElement
|
||||
expect(wrapper).toHaveClass('flex', 'size-full', 'items-center', 'justify-center')
|
||||
})
|
||||
|
||||
it('should render text with correct styling classes', () => {
|
||||
render(<ActualEmptyFolder />)
|
||||
const textElement = screen.getByText(/datasetPipeline\.onlineDrive\.emptyFolder/)
|
||||
expect(textElement).toHaveClass('system-xs-regular', 'text-text-tertiary')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Component Memoization', () => {
|
||||
@@ -1268,58 +1212,56 @@ describe('EmptyFolder', () => {
|
||||
// ==========================================
|
||||
describe('EmptySearchResult', () => {
|
||||
// Get real component for testing
|
||||
const ActualEmptySearchResult = jest.requireActual('./empty-search-result').default
|
||||
let ActualEmptySearchResult: React.ComponentType<{ onResetKeywords: () => void }>
|
||||
|
||||
beforeAll(async () => {
|
||||
const mod = await vi.importActual<{ default: React.ComponentType<{ onResetKeywords: () => void }> }>('./empty-search-result')
|
||||
ActualEmptySearchResult = mod.default
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const onResetKeywords = vi.fn()
|
||||
render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
expect(document.body).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render empty search result message', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const onResetKeywords = vi.fn()
|
||||
render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
expect(screen.getByText(/datasetPipeline\.onlineDrive\.emptySearchResult/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render reset keywords button', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const onResetKeywords = vi.fn()
|
||||
render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
expect(screen.getByText(/datasetPipeline\.onlineDrive\.resetKeywords/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render search icon', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const onResetKeywords = vi.fn()
|
||||
const { container } = render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
const svgElement = container.querySelector('svg')
|
||||
expect(svgElement).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with correct container classes', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const { container } = render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
const wrapper = container.firstChild as HTMLElement
|
||||
expect(wrapper).toHaveClass('flex', 'size-full', 'flex-col', 'items-center', 'justify-center', 'gap-y-2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Props', () => {
|
||||
describe('onResetKeywords prop', () => {
|
||||
it('should call onResetKeywords when button is clicked', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const onResetKeywords = vi.fn()
|
||||
render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(onResetKeywords).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call onResetKeywords on each click', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const onResetKeywords = vi.fn()
|
||||
render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
const button = screen.getByRole('button')
|
||||
fireEvent.click(button)
|
||||
@@ -1338,13 +1280,13 @@ describe('EmptySearchResult', () => {
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('should have accessible button', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const onResetKeywords = vi.fn()
|
||||
render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have readable text content', () => {
|
||||
const onResetKeywords = jest.fn()
|
||||
const onResetKeywords = vi.fn()
|
||||
render(<ActualEmptySearchResult onResetKeywords={onResetKeywords} />)
|
||||
expect(screen.getByText(/datasetPipeline\.onlineDrive\.emptySearchResult/)).toBeInTheDocument()
|
||||
})
|
||||
@@ -1356,10 +1298,16 @@ describe('EmptySearchResult', () => {
|
||||
// ==========================================
|
||||
describe('FileIcon', () => {
|
||||
// Get real component for testing
|
||||
const ActualFileIcon = jest.requireActual('./file-icon').default
|
||||
type FileIconProps = { type: OnlineDriveFileType; fileName: string; size?: 'sm' | 'md' | 'lg' | 'xl'; className?: string }
|
||||
let ActualFileIcon: React.ComponentType<FileIconProps>
|
||||
|
||||
beforeAll(async () => {
|
||||
const mod = await vi.importActual<{ default: React.ComponentType<FileIconProps> }>('./file-icon')
|
||||
ActualFileIcon = mod.default
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -1443,24 +1391,6 @@ describe('FileIcon', () => {
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('className prop', () => {
|
||||
it('should apply custom className to bucket icon', () => {
|
||||
const { container } = render(
|
||||
<ActualFileIcon type={OnlineDriveFileType.bucket} fileName="bucket" className="custom-class" />,
|
||||
)
|
||||
const svg = container.querySelector('svg')
|
||||
expect(svg).toHaveClass('custom-class')
|
||||
})
|
||||
|
||||
it('should apply className to folder icon', () => {
|
||||
const { container } = render(
|
||||
<ActualFileIcon type={OnlineDriveFileType.folder} fileName="folder" className="folder-custom" />,
|
||||
)
|
||||
const svg = container.querySelector('svg')
|
||||
expect(svg).toHaveClass('folder-custom')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Icon Type Determination', () => {
|
||||
@@ -1524,24 +1454,6 @@ describe('FileIcon', () => {
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should apply default size class to bucket icon', () => {
|
||||
const { container } = render(
|
||||
<ActualFileIcon type={OnlineDriveFileType.bucket} fileName="bucket" />,
|
||||
)
|
||||
const svg = container.querySelector('svg')
|
||||
expect(svg).toHaveClass('size-[18px]')
|
||||
})
|
||||
|
||||
it('should apply default size class to folder icon', () => {
|
||||
const { container } = render(
|
||||
<ActualFileIcon type={OnlineDriveFileType.folder} fileName="folder" />,
|
||||
)
|
||||
const svg = container.querySelector('svg')
|
||||
expect(svg).toHaveClass('size-[18px]')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ==========================================
|
||||
@@ -1549,7 +1461,7 @@ describe('FileIcon', () => {
|
||||
// ==========================================
|
||||
describe('Item', () => {
|
||||
// Get real component for testing
|
||||
const ActualItem = jest.requireActual('./item').default
|
||||
let ActualItem: React.ComponentType<ItemProps>
|
||||
|
||||
type ItemProps = {
|
||||
file: OnlineDriveFile
|
||||
@@ -1560,22 +1472,26 @@ describe('Item', () => {
|
||||
onOpen: (file: OnlineDriveFile) => void
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const mod = await vi.importActual<{ default: React.ComponentType<ItemProps> }>('./item')
|
||||
ActualItem = mod.default
|
||||
})
|
||||
|
||||
// Reuse createMockOnlineDriveFile from outer scope
|
||||
const createItemProps = (overrides?: Partial<ItemProps>): ItemProps => ({
|
||||
file: createMockOnlineDriveFile(),
|
||||
isSelected: false,
|
||||
onSelect: jest.fn(),
|
||||
onOpen: jest.fn(),
|
||||
onSelect: vi.fn(),
|
||||
onOpen: vi.fn(),
|
||||
...overrides,
|
||||
})
|
||||
|
||||
// Helper to find custom checkbox element (div-based implementation)
|
||||
const findCheckbox = (container: HTMLElement) => container.querySelector('[data-testid^="checkbox-"]')
|
||||
// Helper to find custom radio element (div-based implementation)
|
||||
const findRadio = (container: HTMLElement) => container.querySelector('.rounded-full.size-4')
|
||||
const getRadio = () => screen.getByRole('radio')
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -1623,8 +1539,8 @@ describe('Item', () => {
|
||||
isMultipleChoice: false,
|
||||
file: createMockOnlineDriveFile({ type: OnlineDriveFileType.file }),
|
||||
})
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
expect(findRadio(container)).toBeInTheDocument()
|
||||
render(<ActualItem {...props} />)
|
||||
expect(getRadio()).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render checkbox or radio for bucket type', () => {
|
||||
@@ -1634,7 +1550,7 @@ describe('Item', () => {
|
||||
})
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
expect(findCheckbox(container)).not.toBeInTheDocument()
|
||||
expect(findRadio(container)).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('radio')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with title attribute for file name', () => {
|
||||
@@ -1666,32 +1582,29 @@ describe('Item', () => {
|
||||
|
||||
it('should show radio as checked when isSelected is true', () => {
|
||||
const props = createItemProps({ isSelected: true, isMultipleChoice: false })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
const radio = findRadio(container)
|
||||
// Checked radio has border-[5px] class
|
||||
expect(radio).toHaveClass('border-[5px]')
|
||||
render(<ActualItem {...props} />)
|
||||
const radio = getRadio()
|
||||
expect(radio).toHaveAttribute('aria-checked', 'true')
|
||||
})
|
||||
})
|
||||
|
||||
describe('disabled prop', () => {
|
||||
it('should apply opacity class when disabled', () => {
|
||||
const props = createItemProps({ disabled: true })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
expect(container.querySelector('.opacity-30')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply disabled styles to checkbox when disabled', () => {
|
||||
const props = createItemProps({ disabled: true, isMultipleChoice: true })
|
||||
it('should not call onSelect when clicking disabled checkbox', () => {
|
||||
const onSelect = vi.fn()
|
||||
const props = createItemProps({ disabled: true, isMultipleChoice: true, onSelect })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
const checkbox = findCheckbox(container)
|
||||
expect(checkbox).toHaveClass('cursor-not-allowed')
|
||||
fireEvent.click(checkbox!)
|
||||
expect(onSelect).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should apply disabled styles to radio when disabled', () => {
|
||||
const props = createItemProps({ disabled: true, isMultipleChoice: false })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
const radio = findRadio(container)
|
||||
expect(radio).toHaveClass('border-components-radio-border-disabled')
|
||||
it('should not call onSelect when clicking disabled radio', () => {
|
||||
const onSelect = vi.fn()
|
||||
const props = createItemProps({ disabled: true, isMultipleChoice: false, onSelect })
|
||||
render(<ActualItem {...props} />)
|
||||
const radio = getRadio()
|
||||
fireEvent.click(radio)
|
||||
expect(onSelect).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1707,13 +1620,13 @@ describe('Item', () => {
|
||||
const props = createItemProps({ isMultipleChoice: true })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
expect(findCheckbox(container)).toBeInTheDocument()
|
||||
expect(findRadio(container)).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('radio')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render radio when false', () => {
|
||||
const props = createItemProps({ isMultipleChoice: false })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
expect(findRadio(container)).toBeInTheDocument()
|
||||
expect(getRadio()).toBeInTheDocument()
|
||||
expect(findCheckbox(container)).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1722,7 +1635,7 @@ describe('Item', () => {
|
||||
describe('User Interactions', () => {
|
||||
describe('Click on Item', () => {
|
||||
it('should call onSelect when clicking on file item', () => {
|
||||
const onSelect = jest.fn()
|
||||
const onSelect = vi.fn()
|
||||
const file = createMockOnlineDriveFile({ type: OnlineDriveFileType.file })
|
||||
const props = createItemProps({ file, onSelect })
|
||||
render(<ActualItem {...props} />)
|
||||
@@ -1731,7 +1644,7 @@ describe('Item', () => {
|
||||
})
|
||||
|
||||
it('should call onOpen when clicking on folder item', () => {
|
||||
const onOpen = jest.fn()
|
||||
const onOpen = vi.fn()
|
||||
const file = createMockOnlineDriveFile({ type: OnlineDriveFileType.folder, name: 'Documents' })
|
||||
const props = createItemProps({ file, onOpen })
|
||||
render(<ActualItem {...props} />)
|
||||
@@ -1740,7 +1653,7 @@ describe('Item', () => {
|
||||
})
|
||||
|
||||
it('should call onOpen when clicking on bucket item', () => {
|
||||
const onOpen = jest.fn()
|
||||
const onOpen = vi.fn()
|
||||
const file = createMockOnlineDriveFile({ type: OnlineDriveFileType.bucket, name: 'my-bucket' })
|
||||
const props = createItemProps({ file, onOpen })
|
||||
render(<ActualItem {...props} />)
|
||||
@@ -1749,8 +1662,8 @@ describe('Item', () => {
|
||||
})
|
||||
|
||||
it('should not call any handler when clicking disabled item', () => {
|
||||
const onSelect = jest.fn()
|
||||
const onOpen = jest.fn()
|
||||
const onSelect = vi.fn()
|
||||
const onOpen = vi.fn()
|
||||
const props = createItemProps({ disabled: true, onSelect, onOpen })
|
||||
render(<ActualItem {...props} />)
|
||||
fireEvent.click(screen.getByText('test-file.txt'))
|
||||
@@ -1761,7 +1674,7 @@ describe('Item', () => {
|
||||
|
||||
describe('Click on Checkbox/Radio', () => {
|
||||
it('should call onSelect when clicking checkbox', () => {
|
||||
const onSelect = jest.fn()
|
||||
const onSelect = vi.fn()
|
||||
const file = createMockOnlineDriveFile()
|
||||
const props = createItemProps({ file, onSelect, isMultipleChoice: true })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
@@ -1771,17 +1684,17 @@ describe('Item', () => {
|
||||
})
|
||||
|
||||
it('should call onSelect when clicking radio', () => {
|
||||
const onSelect = jest.fn()
|
||||
const onSelect = vi.fn()
|
||||
const file = createMockOnlineDriveFile()
|
||||
const props = createItemProps({ file, onSelect, isMultipleChoice: false })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
const radio = findRadio(container)
|
||||
fireEvent.click(radio!)
|
||||
render(<ActualItem {...props} />)
|
||||
const radio = getRadio()
|
||||
fireEvent.click(radio)
|
||||
expect(onSelect).toHaveBeenCalledWith(file)
|
||||
})
|
||||
|
||||
it('should stop event propagation when clicking checkbox', () => {
|
||||
const onSelect = jest.fn()
|
||||
const onSelect = vi.fn()
|
||||
const file = createMockOnlineDriveFile()
|
||||
const props = createItemProps({ file, onSelect, isMultipleChoice: true })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
@@ -1832,58 +1745,6 @@ describe('Item', () => {
|
||||
expect(screen.getByText('5.00 GB')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Styling', () => {
|
||||
it('should have cursor-pointer class', () => {
|
||||
const props = createItemProps()
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
expect(container.firstChild).toHaveClass('cursor-pointer')
|
||||
})
|
||||
|
||||
it('should have hover class', () => {
|
||||
const props = createItemProps()
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
expect(container.firstChild).toHaveClass('hover:bg-state-base-hover')
|
||||
})
|
||||
|
||||
it('should truncate file name', () => {
|
||||
const props = createItemProps()
|
||||
render(<ActualItem {...props} />)
|
||||
const nameElement = screen.getByText('test-file.txt')
|
||||
expect(nameElement).toHaveClass('truncate')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Prop Variations', () => {
|
||||
it.each([
|
||||
{ isSelected: true, isMultipleChoice: true, disabled: false },
|
||||
{ isSelected: true, isMultipleChoice: false, disabled: false },
|
||||
{ isSelected: false, isMultipleChoice: true, disabled: false },
|
||||
{ isSelected: false, isMultipleChoice: false, disabled: false },
|
||||
{ isSelected: true, isMultipleChoice: true, disabled: true },
|
||||
{ isSelected: false, isMultipleChoice: false, disabled: true },
|
||||
])('should render with isSelected=$isSelected, isMultipleChoice=$isMultipleChoice, disabled=$disabled',
|
||||
({ isSelected, isMultipleChoice, disabled }) => {
|
||||
const props = createItemProps({ isSelected, isMultipleChoice, disabled })
|
||||
const { container } = render(<ActualItem {...props} />)
|
||||
if (isMultipleChoice) {
|
||||
const checkbox = findCheckbox(container)
|
||||
expect(checkbox).toBeInTheDocument()
|
||||
if (isSelected)
|
||||
expect(checkbox?.querySelector('[data-testid^="check-icon-"]')).toBeInTheDocument()
|
||||
if (disabled)
|
||||
expect(checkbox).toHaveClass('cursor-not-allowed')
|
||||
}
|
||||
else {
|
||||
const radio = findRadio(container)
|
||||
expect(radio).toBeInTheDocument()
|
||||
if (isSelected)
|
||||
expect(radio).toHaveClass('border-[5px]')
|
||||
if (disabled)
|
||||
expect(radio).toHaveClass('border-components-radio-border-disabled')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ==========================================
|
||||
@@ -1891,8 +1752,17 @@ describe('Item', () => {
|
||||
// ==========================================
|
||||
describe('utils', () => {
|
||||
// Import actual utils functions
|
||||
const { getFileExtension, getFileType } = jest.requireActual('./utils')
|
||||
const { FileAppearanceTypeEnum } = jest.requireActual('@/app/components/base/file-uploader/types')
|
||||
let getFileExtension: (filename: string) => string
|
||||
let getFileType: (filename: string) => string
|
||||
let FileAppearanceTypeEnum: Record<string, string>
|
||||
|
||||
beforeAll(async () => {
|
||||
const utils = await vi.importActual<{ getFileExtension: typeof getFileExtension; getFileType: typeof getFileType }>('./utils')
|
||||
const types = await vi.importActual<{ FileAppearanceTypeEnum: typeof FileAppearanceTypeEnum }>('@/app/components/base/file-uploader/types')
|
||||
getFileExtension = utils.getFileExtension
|
||||
getFileType = utils.getFileType
|
||||
FileAppearanceTypeEnum = types.FileAppearanceTypeEnum
|
||||
})
|
||||
|
||||
describe('getFileExtension', () => {
|
||||
describe('Basic Functionality', () => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { OnlineDriveFile } from '@/models/pipeline'
|
||||
import Item from './item'
|
||||
import EmptyFolder from './empty-folder'
|
||||
@@ -28,6 +29,7 @@ const List = ({
|
||||
isLoading,
|
||||
supportBatchUpload,
|
||||
}: FileListProps) => {
|
||||
const { t } = useTranslation()
|
||||
const anchorRef = useRef<HTMLDivElement>(null)
|
||||
const observerRef = useRef<IntersectionObserver>(null)
|
||||
const dataSourceStore = useDataSourceStore()
|
||||
@@ -87,7 +89,12 @@ const List = ({
|
||||
}
|
||||
{
|
||||
isPartialLoading && (
|
||||
<div className='flex items-center justify-center py-2'>
|
||||
<div
|
||||
className='flex items-center justify-center py-2'
|
||||
role='status'
|
||||
aria-live='polite'
|
||||
aria-label={t('appApi.loading')}
|
||||
>
|
||||
<RiLoader2Line className='animation-spin size-4 text-text-tertiary' />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -13,44 +13,53 @@ import type { OnlineDriveData } from '@/types/pipeline'
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock useDocLink - context hook requires mocking
|
||||
const mockDocLink = jest.fn((path?: string) => `https://docs.example.com${path || ''}`)
|
||||
jest.mock('@/context/i18n', () => ({
|
||||
const mockDocLink = vi.fn((path?: string) => `https://docs.example.com${path || ''}`)
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => mockDocLink,
|
||||
}))
|
||||
|
||||
// Mock dataset-detail context - context provider requires mocking
|
||||
let mockPipelineId: string | undefined = 'pipeline-123'
|
||||
jest.mock('@/context/dataset-detail', () => ({
|
||||
vi.mock('@/context/dataset-detail', () => ({
|
||||
useDatasetDetailContextWithSelector: (selector: (s: any) => any) => selector({ dataset: { pipeline_id: mockPipelineId } }),
|
||||
}))
|
||||
|
||||
// Mock modal context - context provider requires mocking
|
||||
const mockSetShowAccountSettingModal = jest.fn()
|
||||
jest.mock('@/context/modal-context', () => ({
|
||||
const mockSetShowAccountSettingModal = vi.fn()
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContextSelector: (selector: (s: any) => any) => selector({ setShowAccountSettingModal: mockSetShowAccountSettingModal }),
|
||||
}))
|
||||
|
||||
// Mock ssePost - API service requires mocking
|
||||
const mockSsePost = jest.fn()
|
||||
jest.mock('@/service/base', () => ({
|
||||
ssePost: (...args: any[]) => mockSsePost(...args),
|
||||
const { mockSsePost } = vi.hoisted(() => ({
|
||||
mockSsePost: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/base', () => ({
|
||||
ssePost: mockSsePost,
|
||||
}))
|
||||
|
||||
// Mock useGetDataSourceAuth - API service hook requires mocking
|
||||
const mockUseGetDataSourceAuth = jest.fn()
|
||||
jest.mock('@/service/use-datasource', () => ({
|
||||
useGetDataSourceAuth: (params: any) => mockUseGetDataSourceAuth(params),
|
||||
const { mockUseGetDataSourceAuth } = vi.hoisted(() => ({
|
||||
mockUseGetDataSourceAuth: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-datasource', () => ({
|
||||
useGetDataSourceAuth: mockUseGetDataSourceAuth,
|
||||
}))
|
||||
|
||||
// Mock Toast
|
||||
const mockToastNotify = jest.fn()
|
||||
jest.mock('@/app/components/base/toast', () => ({
|
||||
const { mockToastNotify } = vi.hoisted(() => ({
|
||||
mockToastNotify: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/toast', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
notify: (...args: any[]) => mockToastNotify(...args),
|
||||
notify: mockToastNotify,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -68,26 +77,26 @@ const mockStoreState = {
|
||||
currentCredentialId: '',
|
||||
isTruncated: { current: false },
|
||||
currentNextPageParametersRef: { current: {} },
|
||||
setOnlineDriveFileList: jest.fn(),
|
||||
setKeywords: jest.fn(),
|
||||
setSelectedFileIds: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
setPrefix: jest.fn(),
|
||||
setBucket: jest.fn(),
|
||||
setHasBucket: jest.fn(),
|
||||
setOnlineDriveFileList: vi.fn(),
|
||||
setKeywords: vi.fn(),
|
||||
setSelectedFileIds: vi.fn(),
|
||||
setBreadcrumbs: vi.fn(),
|
||||
setPrefix: vi.fn(),
|
||||
setBucket: vi.fn(),
|
||||
setHasBucket: vi.fn(),
|
||||
}
|
||||
|
||||
const mockGetState = jest.fn(() => mockStoreState)
|
||||
const mockGetState = vi.fn(() => mockStoreState)
|
||||
const mockDataSourceStore = { getState: mockGetState }
|
||||
|
||||
jest.mock('../store', () => ({
|
||||
vi.mock('../store', () => ({
|
||||
useDataSourceStoreWithSelector: (selector: (s: any) => any) => selector(mockStoreState),
|
||||
useDataSourceStore: () => mockDataSourceStore,
|
||||
}))
|
||||
|
||||
// Mock Header component
|
||||
jest.mock('../base/header', () => {
|
||||
const MockHeader = (props: any) => (
|
||||
vi.mock('../base/header', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="header">
|
||||
<span data-testid="header-doc-title">{props.docTitle}</span>
|
||||
<span data-testid="header-doc-link">{props.docLink}</span>
|
||||
@@ -97,13 +106,12 @@ jest.mock('../base/header', () => {
|
||||
<button data-testid="header-credential-change" onClick={() => props.onCredentialChange('new-cred-id')}>Change Credential</button>
|
||||
<span data-testid="header-credentials-count">{props.credentials?.length || 0}</span>
|
||||
</div>
|
||||
)
|
||||
return MockHeader
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock FileList component
|
||||
jest.mock('./file-list', () => {
|
||||
const MockFileList = (props: any) => (
|
||||
vi.mock('./file-list', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="file-list">
|
||||
<span data-testid="file-list-count">{props.fileList?.length || 0}</span>
|
||||
<span data-testid="file-list-selected-count">{props.selectedFileIds?.length || 0}</span>
|
||||
@@ -164,9 +172,8 @@ jest.mock('./file-list', () => {
|
||||
Open File
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
return MockFileList
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// ==========================================
|
||||
// Test Data Builders
|
||||
@@ -206,7 +213,7 @@ type OnlineDriveProps = React.ComponentProps<typeof OnlineDrive>
|
||||
const createDefaultProps = (overrides?: Partial<OnlineDriveProps>): OnlineDriveProps => ({
|
||||
nodeId: 'node-1',
|
||||
nodeData: createMockNodeData(),
|
||||
onCredentialChange: jest.fn(),
|
||||
onCredentialChange: vi.fn(),
|
||||
isInPipeline: false,
|
||||
supportBatchUpload: true,
|
||||
...overrides,
|
||||
@@ -226,13 +233,13 @@ const resetMockStoreState = () => {
|
||||
mockStoreState.currentCredentialId = ''
|
||||
mockStoreState.isTruncated = { current: false }
|
||||
mockStoreState.currentNextPageParametersRef = { current: {} }
|
||||
mockStoreState.setOnlineDriveFileList = jest.fn()
|
||||
mockStoreState.setKeywords = jest.fn()
|
||||
mockStoreState.setSelectedFileIds = jest.fn()
|
||||
mockStoreState.setBreadcrumbs = jest.fn()
|
||||
mockStoreState.setPrefix = jest.fn()
|
||||
mockStoreState.setBucket = jest.fn()
|
||||
mockStoreState.setHasBucket = jest.fn()
|
||||
mockStoreState.setOnlineDriveFileList = vi.fn()
|
||||
mockStoreState.setKeywords = vi.fn()
|
||||
mockStoreState.setSelectedFileIds = vi.fn()
|
||||
mockStoreState.setBreadcrumbs = vi.fn()
|
||||
mockStoreState.setPrefix = vi.fn()
|
||||
mockStoreState.setBucket = vi.fn()
|
||||
mockStoreState.setHasBucket = vi.fn()
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
@@ -240,7 +247,7 @@ const resetMockStoreState = () => {
|
||||
// ==========================================
|
||||
describe('OnlineDrive', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Reset store state
|
||||
resetMockStoreState()
|
||||
@@ -498,7 +505,7 @@ describe('OnlineDrive', () => {
|
||||
describe('onCredentialChange prop', () => {
|
||||
it('should call onCredentialChange with credential id', () => {
|
||||
// Arrange
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
|
||||
// Act
|
||||
@@ -847,7 +854,7 @@ describe('OnlineDrive', () => {
|
||||
describe('Credential Change', () => {
|
||||
it('should call onCredentialChange prop', () => {
|
||||
// Arrange
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
render(<OnlineDrive {...props} />)
|
||||
|
||||
@@ -1296,14 +1303,14 @@ describe('OnlineDrive', () => {
|
||||
// ==========================================
|
||||
describe('Header', () => {
|
||||
const createHeaderProps = (overrides?: Partial<React.ComponentProps<typeof Header>>) => ({
|
||||
onClickConfiguration: jest.fn(),
|
||||
onClickConfiguration: vi.fn(),
|
||||
docTitle: 'Documentation',
|
||||
docLink: 'https://docs.example.com/guide',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -1398,7 +1405,7 @@ describe('Header', () => {
|
||||
describe('onClickConfiguration prop', () => {
|
||||
it('should call onClickConfiguration when configuration icon is clicked', () => {
|
||||
// Arrange
|
||||
const mockOnClickConfiguration = jest.fn()
|
||||
const mockOnClickConfiguration = vi.fn()
|
||||
const props = createHeaderProps({ onClickConfiguration: mockOnClickConfiguration })
|
||||
|
||||
// Act
|
||||
|
||||
@@ -34,12 +34,12 @@ const createMockCrawlResultItems = (count = 3): CrawlResultItemType[] => {
|
||||
describe('CheckboxWithLabel', () => {
|
||||
const defaultProps = {
|
||||
isChecked: false,
|
||||
onChange: jest.fn(),
|
||||
onChange: vi.fn(),
|
||||
label: 'Test Label',
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -114,7 +114,7 @@ describe('CheckboxWithLabel', () => {
|
||||
describe('User Interactions', () => {
|
||||
it('should call onChange with true when clicking unchecked checkbox', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const { container } = render(<CheckboxWithLabel {...defaultProps} isChecked={false} onChange={mockOnChange} />)
|
||||
|
||||
// Act
|
||||
@@ -127,7 +127,7 @@ describe('CheckboxWithLabel', () => {
|
||||
|
||||
it('should call onChange with false when clicking checked checkbox', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
const { container } = render(<CheckboxWithLabel {...defaultProps} isChecked={true} onChange={mockOnChange} />)
|
||||
|
||||
// Act
|
||||
@@ -140,7 +140,7 @@ describe('CheckboxWithLabel', () => {
|
||||
|
||||
it('should not trigger onChange when clicking label text due to custom checkbox', () => {
|
||||
// Arrange
|
||||
const mockOnChange = jest.fn()
|
||||
const mockOnChange = vi.fn()
|
||||
render(<CheckboxWithLabel {...defaultProps} onChange={mockOnChange} />)
|
||||
|
||||
// Act - Click on the label text element
|
||||
@@ -160,14 +160,14 @@ describe('CrawledResultItem', () => {
|
||||
const defaultProps = {
|
||||
payload: createMockCrawlResultItem(),
|
||||
isChecked: false,
|
||||
onCheckChange: jest.fn(),
|
||||
onCheckChange: vi.fn(),
|
||||
isPreview: false,
|
||||
showPreview: true,
|
||||
onPreview: jest.fn(),
|
||||
onPreview: vi.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -282,7 +282,7 @@ describe('CrawledResultItem', () => {
|
||||
describe('User Interactions', () => {
|
||||
it('should call onCheckChange with true when clicking unchecked checkbox', () => {
|
||||
// Arrange
|
||||
const mockOnCheckChange = jest.fn()
|
||||
const mockOnCheckChange = vi.fn()
|
||||
const { container } = render(
|
||||
<CrawledResultItem
|
||||
{...defaultProps}
|
||||
@@ -301,7 +301,7 @@ describe('CrawledResultItem', () => {
|
||||
|
||||
it('should call onCheckChange with false when clicking checked checkbox', () => {
|
||||
// Arrange
|
||||
const mockOnCheckChange = jest.fn()
|
||||
const mockOnCheckChange = vi.fn()
|
||||
const { container } = render(
|
||||
<CrawledResultItem
|
||||
{...defaultProps}
|
||||
@@ -320,7 +320,7 @@ describe('CrawledResultItem', () => {
|
||||
|
||||
it('should call onPreview when clicking preview button', () => {
|
||||
// Arrange
|
||||
const mockOnPreview = jest.fn()
|
||||
const mockOnPreview = vi.fn()
|
||||
render(<CrawledResultItem {...defaultProps} onPreview={mockOnPreview} />)
|
||||
|
||||
// Act
|
||||
@@ -332,7 +332,7 @@ describe('CrawledResultItem', () => {
|
||||
|
||||
it('should toggle radio state when isMultipleChoice is false', () => {
|
||||
// Arrange
|
||||
const mockOnCheckChange = jest.fn()
|
||||
const mockOnCheckChange = vi.fn()
|
||||
const { container } = render(
|
||||
<CrawledResultItem
|
||||
{...defaultProps}
|
||||
@@ -359,12 +359,12 @@ describe('CrawledResult', () => {
|
||||
const defaultProps = {
|
||||
list: createMockCrawlResultItems(3),
|
||||
checkedList: [] as CrawlResultItemType[],
|
||||
onSelectedChange: jest.fn(),
|
||||
onSelectedChange: vi.fn(),
|
||||
usedTime: 1.5,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -478,7 +478,7 @@ describe('CrawledResult', () => {
|
||||
describe('User Interactions', () => {
|
||||
it('should call onSelectedChange with all items when clicking select all', () => {
|
||||
// Arrange
|
||||
const mockOnSelectedChange = jest.fn()
|
||||
const mockOnSelectedChange = vi.fn()
|
||||
const list = createMockCrawlResultItems(3)
|
||||
const { container } = render(
|
||||
<CrawledResult
|
||||
@@ -499,7 +499,7 @@ describe('CrawledResult', () => {
|
||||
|
||||
it('should call onSelectedChange with empty array when clicking reset all', () => {
|
||||
// Arrange
|
||||
const mockOnSelectedChange = jest.fn()
|
||||
const mockOnSelectedChange = vi.fn()
|
||||
const list = createMockCrawlResultItems(3)
|
||||
const { container } = render(
|
||||
<CrawledResult
|
||||
@@ -520,7 +520,7 @@ describe('CrawledResult', () => {
|
||||
|
||||
it('should add item to checkedList when checking unchecked item', () => {
|
||||
// Arrange
|
||||
const mockOnSelectedChange = jest.fn()
|
||||
const mockOnSelectedChange = vi.fn()
|
||||
const list = createMockCrawlResultItems(3)
|
||||
const { container } = render(
|
||||
<CrawledResult
|
||||
@@ -541,7 +541,7 @@ describe('CrawledResult', () => {
|
||||
|
||||
it('should remove item from checkedList when unchecking checked item', () => {
|
||||
// Arrange
|
||||
const mockOnSelectedChange = jest.fn()
|
||||
const mockOnSelectedChange = vi.fn()
|
||||
const list = createMockCrawlResultItems(3)
|
||||
const { container } = render(
|
||||
<CrawledResult
|
||||
@@ -562,7 +562,7 @@ describe('CrawledResult', () => {
|
||||
|
||||
it('should replace selection when checking in single choice mode', () => {
|
||||
// Arrange
|
||||
const mockOnSelectedChange = jest.fn()
|
||||
const mockOnSelectedChange = vi.fn()
|
||||
const list = createMockCrawlResultItems(3)
|
||||
const { container } = render(
|
||||
<CrawledResult
|
||||
@@ -584,7 +584,7 @@ describe('CrawledResult', () => {
|
||||
|
||||
it('should call onPreview with item and index when clicking preview', () => {
|
||||
// Arrange
|
||||
const mockOnPreview = jest.fn()
|
||||
const mockOnPreview = vi.fn()
|
||||
const list = createMockCrawlResultItems(3)
|
||||
render(
|
||||
<CrawledResult
|
||||
@@ -664,7 +664,7 @@ describe('Crawling', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -753,7 +753,7 @@ describe('ErrorMessage', () => {
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
describe('Rendering', () => {
|
||||
@@ -883,7 +883,7 @@ describe('Base Components Integration', () => {
|
||||
<CrawledResult
|
||||
list={list}
|
||||
checkedList={[]}
|
||||
onSelectedChange={jest.fn()}
|
||||
onSelectedChange={vi.fn()}
|
||||
usedTime={1.0}
|
||||
/>,
|
||||
)
|
||||
@@ -902,7 +902,7 @@ describe('Base Components Integration', () => {
|
||||
<CrawledResult
|
||||
list={list}
|
||||
checkedList={[]}
|
||||
onSelectedChange={jest.fn()}
|
||||
onSelectedChange={vi.fn()}
|
||||
usedTime={1.0}
|
||||
isMultipleChoice={true}
|
||||
/>,
|
||||
@@ -916,8 +916,8 @@ describe('Base Components Integration', () => {
|
||||
it('should allow selecting and previewing items', () => {
|
||||
// Arrange
|
||||
const list = createMockCrawlResultItems(3)
|
||||
const mockOnSelectedChange = jest.fn()
|
||||
const mockOnPreview = jest.fn()
|
||||
const mockOnSelectedChange = vi.fn()
|
||||
const mockOnPreview = vi.fn()
|
||||
|
||||
const { container } = render(
|
||||
<CrawledResult
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { MockInstance } from 'vitest'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
import Options from './index'
|
||||
@@ -11,19 +12,22 @@ import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/ty
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock useInitialData and useConfigurations hooks
|
||||
const mockUseInitialData = jest.fn()
|
||||
const mockUseConfigurations = jest.fn()
|
||||
jest.mock('@/app/components/rag-pipeline/hooks/use-input-fields', () => ({
|
||||
useInitialData: (...args: any[]) => mockUseInitialData(...args),
|
||||
useConfigurations: (...args: any[]) => mockUseConfigurations(...args),
|
||||
const { mockUseInitialData, mockUseConfigurations } = vi.hoisted(() => ({
|
||||
mockUseInitialData: vi.fn(),
|
||||
mockUseConfigurations: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/rag-pipeline/hooks/use-input-fields', () => ({
|
||||
useInitialData: mockUseInitialData,
|
||||
useConfigurations: mockUseConfigurations,
|
||||
}))
|
||||
|
||||
// Mock BaseField
|
||||
const mockBaseField = jest.fn()
|
||||
jest.mock('@/app/components/base/form/form-scenarios/base/field', () => {
|
||||
const mockBaseField = vi.fn()
|
||||
vi.mock('@/app/components/base/form/form-scenarios/base/field', () => {
|
||||
const MockBaseFieldFactory = (props: any) => {
|
||||
mockBaseField(props)
|
||||
const MockField = ({ form }: { form: any }) => (
|
||||
@@ -38,13 +42,13 @@ jest.mock('@/app/components/base/form/form-scenarios/base/field', () => {
|
||||
)
|
||||
return MockField
|
||||
}
|
||||
return MockBaseFieldFactory
|
||||
return { default: MockBaseFieldFactory }
|
||||
})
|
||||
|
||||
// Mock useAppForm
|
||||
const mockHandleSubmit = jest.fn()
|
||||
const mockHandleSubmit = vi.fn()
|
||||
const mockFormValues: Record<string, any> = {}
|
||||
jest.mock('@/app/components/base/form', () => ({
|
||||
vi.mock('@/app/components/base/form', () => ({
|
||||
useAppForm: (options: any) => {
|
||||
const formOptions = options
|
||||
return {
|
||||
@@ -106,7 +110,7 @@ const createDefaultProps = (overrides?: Partial<OptionsProps>): OptionsProps =>
|
||||
variables: createMockVariables(),
|
||||
step: CrawlStep.init,
|
||||
runDisabled: false,
|
||||
onSubmit: jest.fn(),
|
||||
onSubmit: vi.fn(),
|
||||
...overrides,
|
||||
})
|
||||
|
||||
@@ -114,13 +118,13 @@ const createDefaultProps = (overrides?: Partial<OptionsProps>): OptionsProps =>
|
||||
// Test Suites
|
||||
// ==========================================
|
||||
describe('Options', () => {
|
||||
let toastNotifySpy: jest.SpyInstance
|
||||
let toastNotifySpy: MockInstance
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Spy on Toast.notify instead of mocking the entire module
|
||||
toastNotifySpy = jest.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: jest.fn() }))
|
||||
toastNotifySpy = vi.spyOn(Toast, 'notify').mockImplementation(() => ({ clear: vi.fn() }))
|
||||
|
||||
// Reset mock form values
|
||||
Object.keys(mockFormValues).forEach(key => delete mockFormValues[key])
|
||||
@@ -379,7 +383,7 @@ describe('Options', () => {
|
||||
type: BaseFieldType.textInput,
|
||||
})
|
||||
mockUseConfigurations.mockReturnValue([config])
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
const props = createDefaultProps({ onSubmit: mockOnSubmit })
|
||||
|
||||
// Act
|
||||
@@ -392,7 +396,7 @@ describe('Options', () => {
|
||||
|
||||
it('should not call onSubmit when validation fails', () => {
|
||||
// Arrange
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
// Create a required field configuration
|
||||
const requiredConfig = createMockConfiguration({
|
||||
variable: 'url',
|
||||
@@ -421,7 +425,7 @@ describe('Options', () => {
|
||||
mockUseConfigurations.mockReturnValue(configs)
|
||||
mockFormValues.url = 'https://example.com'
|
||||
mockFormValues.depth = 2
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
const props = createDefaultProps({ onSubmit: mockOnSubmit })
|
||||
|
||||
// Act
|
||||
@@ -591,7 +595,7 @@ describe('Options', () => {
|
||||
required: false, // Not required so validation passes with empty value
|
||||
})
|
||||
mockUseConfigurations.mockReturnValue([config])
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
const props = createDefaultProps({ onSubmit: mockOnSubmit })
|
||||
render(<Options {...props} />)
|
||||
|
||||
@@ -635,8 +639,8 @@ describe('Options', () => {
|
||||
|
||||
// Act
|
||||
const form = container.querySelector('form')!
|
||||
const mockPreventDefault = jest.fn()
|
||||
const mockStopPropagation = jest.fn()
|
||||
const mockPreventDefault = vi.fn()
|
||||
const mockStopPropagation = vi.fn()
|
||||
|
||||
fireEvent.submit(form, {
|
||||
preventDefault: mockPreventDefault,
|
||||
@@ -655,7 +659,7 @@ describe('Options', () => {
|
||||
type: BaseFieldType.textInput,
|
||||
})
|
||||
mockUseConfigurations.mockReturnValue([config])
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
const props = createDefaultProps({ onSubmit: mockOnSubmit })
|
||||
render(<Options {...props} />)
|
||||
|
||||
@@ -668,7 +672,7 @@ describe('Options', () => {
|
||||
|
||||
it('should not trigger submit when button is disabled', () => {
|
||||
// Arrange
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
const props = createDefaultProps({ onSubmit: mockOnSubmit, runDisabled: true })
|
||||
render(<Options {...props} />)
|
||||
|
||||
@@ -837,7 +841,7 @@ describe('Options', () => {
|
||||
})
|
||||
mockUseConfigurations.mockReturnValue([requiredConfig])
|
||||
mockFormValues.url = 'https://example.com' // Provide valid value
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
const props = createDefaultProps({ onSubmit: mockOnSubmit })
|
||||
render(<Options {...props} />)
|
||||
|
||||
@@ -947,7 +951,7 @@ describe('Options', () => {
|
||||
type: BaseFieldType.textInput,
|
||||
})
|
||||
mockUseConfigurations.mockReturnValue([config])
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
const props = createDefaultProps({ onSubmit: mockOnSubmit })
|
||||
render(<Options {...props} />)
|
||||
|
||||
@@ -968,7 +972,7 @@ describe('Options', () => {
|
||||
type: BaseFieldType.textInput,
|
||||
})
|
||||
mockUseConfigurations.mockReturnValue([config])
|
||||
const mockOnSubmit = jest.fn()
|
||||
const mockOnSubmit = vi.fn()
|
||||
const props = createDefaultProps({ onSubmit: mockOnSubmit })
|
||||
render(<Options {...props} />)
|
||||
|
||||
|
||||
@@ -10,44 +10,53 @@ import { ACCOUNT_SETTING_TAB } from '@/app/components/header/account-setting/con
|
||||
// Mock Modules
|
||||
// ==========================================
|
||||
|
||||
// Note: react-i18next uses global mock from web/__mocks__/react-i18next.ts
|
||||
// Note: react-i18next uses global mock from web/vitest.setup.ts
|
||||
|
||||
// Mock useDocLink - context hook requires mocking
|
||||
const mockDocLink = jest.fn((path?: string) => `https://docs.example.com${path || ''}`)
|
||||
jest.mock('@/context/i18n', () => ({
|
||||
const mockDocLink = vi.fn((path?: string) => `https://docs.example.com${path || ''}`)
|
||||
vi.mock('@/context/i18n', () => ({
|
||||
useDocLink: () => mockDocLink,
|
||||
}))
|
||||
|
||||
// Mock dataset-detail context - context provider requires mocking
|
||||
let mockPipelineId: string | undefined = 'pipeline-123'
|
||||
jest.mock('@/context/dataset-detail', () => ({
|
||||
vi.mock('@/context/dataset-detail', () => ({
|
||||
useDatasetDetailContextWithSelector: (selector: (s: any) => any) => selector({ dataset: { pipeline_id: mockPipelineId } }),
|
||||
}))
|
||||
|
||||
// Mock modal context - context provider requires mocking
|
||||
const mockSetShowAccountSettingModal = jest.fn()
|
||||
jest.mock('@/context/modal-context', () => ({
|
||||
const mockSetShowAccountSettingModal = vi.fn()
|
||||
vi.mock('@/context/modal-context', () => ({
|
||||
useModalContextSelector: (selector: (s: any) => any) => selector({ setShowAccountSettingModal: mockSetShowAccountSettingModal }),
|
||||
}))
|
||||
|
||||
// Mock ssePost - API service requires mocking
|
||||
const mockSsePost = jest.fn()
|
||||
jest.mock('@/service/base', () => ({
|
||||
ssePost: (...args: any[]) => mockSsePost(...args),
|
||||
const { mockSsePost } = vi.hoisted(() => ({
|
||||
mockSsePost: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/base', () => ({
|
||||
ssePost: mockSsePost,
|
||||
}))
|
||||
|
||||
// Mock useGetDataSourceAuth - API service hook requires mocking
|
||||
const mockUseGetDataSourceAuth = jest.fn()
|
||||
jest.mock('@/service/use-datasource', () => ({
|
||||
useGetDataSourceAuth: (params: any) => mockUseGetDataSourceAuth(params),
|
||||
const { mockUseGetDataSourceAuth } = vi.hoisted(() => ({
|
||||
mockUseGetDataSourceAuth: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-datasource', () => ({
|
||||
useGetDataSourceAuth: mockUseGetDataSourceAuth,
|
||||
}))
|
||||
|
||||
// Mock usePipeline hooks - API service hooks require mocking
|
||||
const mockUseDraftPipelinePreProcessingParams = jest.fn()
|
||||
const mockUsePublishedPipelinePreProcessingParams = jest.fn()
|
||||
jest.mock('@/service/use-pipeline', () => ({
|
||||
useDraftPipelinePreProcessingParams: (...args: any[]) => mockUseDraftPipelinePreProcessingParams(...args),
|
||||
usePublishedPipelinePreProcessingParams: (...args: any[]) => mockUsePublishedPipelinePreProcessingParams(...args),
|
||||
const { mockUseDraftPipelinePreProcessingParams, mockUsePublishedPipelinePreProcessingParams } = vi.hoisted(() => ({
|
||||
mockUseDraftPipelinePreProcessingParams: vi.fn(),
|
||||
mockUsePublishedPipelinePreProcessingParams: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-pipeline', () => ({
|
||||
useDraftPipelinePreProcessingParams: mockUseDraftPipelinePreProcessingParams,
|
||||
usePublishedPipelinePreProcessingParams: mockUsePublishedPipelinePreProcessingParams,
|
||||
}))
|
||||
|
||||
// Note: zustand/react/shallow useShallow is imported directly (simple utility function)
|
||||
@@ -59,24 +68,24 @@ const mockStoreState = {
|
||||
websitePages: [] as CrawlResultItem[],
|
||||
previewIndex: -1,
|
||||
currentCredentialId: '',
|
||||
setWebsitePages: jest.fn(),
|
||||
setCurrentWebsite: jest.fn(),
|
||||
setPreviewIndex: jest.fn(),
|
||||
setStep: jest.fn(),
|
||||
setCrawlResult: jest.fn(),
|
||||
setWebsitePages: vi.fn(),
|
||||
setCurrentWebsite: vi.fn(),
|
||||
setPreviewIndex: vi.fn(),
|
||||
setStep: vi.fn(),
|
||||
setCrawlResult: vi.fn(),
|
||||
}
|
||||
|
||||
const mockGetState = jest.fn(() => mockStoreState)
|
||||
const mockGetState = vi.fn(() => mockStoreState)
|
||||
const mockDataSourceStore = { getState: mockGetState }
|
||||
|
||||
jest.mock('../store', () => ({
|
||||
vi.mock('../store', () => ({
|
||||
useDataSourceStoreWithSelector: (selector: (s: any) => any) => selector(mockStoreState),
|
||||
useDataSourceStore: () => mockDataSourceStore,
|
||||
}))
|
||||
|
||||
// Mock Header component
|
||||
jest.mock('../base/header', () => {
|
||||
const MockHeader = (props: any) => (
|
||||
vi.mock('../base/header', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="header">
|
||||
<span data-testid="header-doc-title">{props.docTitle}</span>
|
||||
<span data-testid="header-doc-link">{props.docLink}</span>
|
||||
@@ -86,14 +95,13 @@ jest.mock('../base/header', () => {
|
||||
<button data-testid="header-credential-change" onClick={() => props.onCredentialChange('new-cred-id')}>Change Credential</button>
|
||||
<span data-testid="header-credentials-count">{props.credentials?.length || 0}</span>
|
||||
</div>
|
||||
)
|
||||
return MockHeader
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Options component
|
||||
const mockOptionsSubmit = jest.fn()
|
||||
jest.mock('./base/options', () => {
|
||||
const MockOptions = (props: any) => (
|
||||
const mockOptionsSubmit = vi.fn()
|
||||
vi.mock('./base/options', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="options">
|
||||
<span data-testid="options-step">{props.step}</span>
|
||||
<span data-testid="options-run-disabled">{String(props.runDisabled)}</span>
|
||||
@@ -108,35 +116,32 @@ jest.mock('./base/options', () => {
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
return MockOptions
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Crawling component
|
||||
jest.mock('./base/crawling', () => {
|
||||
const MockCrawling = (props: any) => (
|
||||
vi.mock('./base/crawling', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="crawling">
|
||||
<span data-testid="crawling-crawled-num">{props.crawledNum}</span>
|
||||
<span data-testid="crawling-total-num">{props.totalNum}</span>
|
||||
</div>
|
||||
)
|
||||
return MockCrawling
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock ErrorMessage component
|
||||
jest.mock('./base/error-message', () => {
|
||||
const MockErrorMessage = (props: any) => (
|
||||
vi.mock('./base/error-message', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="error-message" className={props.className}>
|
||||
<span data-testid="error-title">{props.title}</span>
|
||||
<span data-testid="error-msg">{props.errorMsg}</span>
|
||||
</div>
|
||||
)
|
||||
return MockErrorMessage
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock CrawledResult component
|
||||
jest.mock('./base/crawled-result', () => {
|
||||
const MockCrawledResult = (props: any) => (
|
||||
vi.mock('./base/crawled-result', () => ({
|
||||
default: (props: any) => (
|
||||
<div data-testid="crawled-result" className={props.className}>
|
||||
<span data-testid="crawled-result-count">{props.list?.length || 0}</span>
|
||||
<span data-testid="crawled-result-checked-count">{props.checkedList?.length || 0}</span>
|
||||
@@ -157,9 +162,8 @@ jest.mock('./base/crawled-result', () => {
|
||||
Preview
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
return MockCrawledResult
|
||||
})
|
||||
),
|
||||
}))
|
||||
|
||||
// ==========================================
|
||||
// Test Data Builders
|
||||
@@ -199,7 +203,7 @@ type WebsiteCrawlProps = React.ComponentProps<typeof WebsiteCrawl>
|
||||
const createDefaultProps = (overrides?: Partial<WebsiteCrawlProps>): WebsiteCrawlProps => ({
|
||||
nodeId: 'node-1',
|
||||
nodeData: createMockNodeData(),
|
||||
onCredentialChange: jest.fn(),
|
||||
onCredentialChange: vi.fn(),
|
||||
isInPipeline: false,
|
||||
supportBatchUpload: true,
|
||||
...overrides,
|
||||
@@ -210,7 +214,7 @@ const createDefaultProps = (overrides?: Partial<WebsiteCrawlProps>): WebsiteCraw
|
||||
// ==========================================
|
||||
describe('WebsiteCrawl', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
vi.clearAllMocks()
|
||||
|
||||
// Reset store state
|
||||
mockStoreState.crawlResult = undefined
|
||||
@@ -218,11 +222,11 @@ describe('WebsiteCrawl', () => {
|
||||
mockStoreState.websitePages = []
|
||||
mockStoreState.previewIndex = -1
|
||||
mockStoreState.currentCredentialId = ''
|
||||
mockStoreState.setWebsitePages = jest.fn()
|
||||
mockStoreState.setCurrentWebsite = jest.fn()
|
||||
mockStoreState.setPreviewIndex = jest.fn()
|
||||
mockStoreState.setStep = jest.fn()
|
||||
mockStoreState.setCrawlResult = jest.fn()
|
||||
mockStoreState.setWebsitePages = vi.fn()
|
||||
mockStoreState.setCurrentWebsite = vi.fn()
|
||||
mockStoreState.setPreviewIndex = vi.fn()
|
||||
mockStoreState.setStep = vi.fn()
|
||||
mockStoreState.setCrawlResult = vi.fn()
|
||||
|
||||
// Reset context values
|
||||
mockPipelineId = 'pipeline-123'
|
||||
@@ -511,7 +515,7 @@ describe('WebsiteCrawl', () => {
|
||||
describe('onCredentialChange prop', () => {
|
||||
it('should call onCredentialChange with credential id and reset state', () => {
|
||||
// Arrange
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
|
||||
// Act
|
||||
@@ -684,7 +688,7 @@ describe('WebsiteCrawl', () => {
|
||||
|
||||
it('should have stable handleCredentialChange that resets state', () => {
|
||||
// Arrange
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
render(<WebsiteCrawl {...props} />)
|
||||
|
||||
@@ -732,7 +736,7 @@ describe('WebsiteCrawl', () => {
|
||||
|
||||
it('should handle credential change', () => {
|
||||
// Arrange
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
render(<WebsiteCrawl {...props} />)
|
||||
|
||||
@@ -1263,7 +1267,7 @@ describe('WebsiteCrawl', () => {
|
||||
const props: WebsiteCrawlProps = {
|
||||
nodeId: 'node-1',
|
||||
nodeData: createMockNodeData(),
|
||||
onCredentialChange: jest.fn(),
|
||||
onCredentialChange: vi.fn(),
|
||||
// isInPipeline and supportBatchUpload are not provided
|
||||
}
|
||||
|
||||
@@ -1399,7 +1403,7 @@ describe('WebsiteCrawl', () => {
|
||||
it('should handle credential change and allow new crawl', () => {
|
||||
// Arrange
|
||||
mockStoreState.currentCredentialId = 'initial-cred'
|
||||
const mockOnCredentialChange = jest.fn()
|
||||
const mockOnCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange: mockOnCredentialChange })
|
||||
|
||||
// Act
|
||||
@@ -1453,7 +1457,7 @@ describe('WebsiteCrawl', () => {
|
||||
|
||||
it('should not re-run callbacks when props are the same', () => {
|
||||
// Arrange
|
||||
const onCredentialChange = jest.fn()
|
||||
const onCredentialChange = vi.fn()
|
||||
const props = createDefaultProps({ onCredentialChange })
|
||||
|
||||
// Act
|
||||
|
||||
Reference in New Issue
Block a user