mirror of https://github.com/langgenius/dify.git
1205 lines
40 KiB
TypeScript
1205 lines
40 KiB
TypeScript
import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types'
|
|
import type { NotionPage } from '@/models/common'
|
|
import type { CrawlOptions, CrawlResultItem, DataSet, FileItem } from '@/models/datasets'
|
|
import { act, fireEvent, render, renderHook, screen } from '@testing-library/react'
|
|
import { Plan } from '@/app/components/billing/type'
|
|
import { DataSourceType } from '@/models/datasets'
|
|
import { DataSourceTypeSelector, NextStepButton, PreviewPanel } from './components'
|
|
import { usePreviewState } from './hooks'
|
|
import StepOne from './index'
|
|
|
|
// ==========================================
|
|
// Mock External Dependencies
|
|
// ==========================================
|
|
|
|
// Mock config for website crawl features
|
|
vi.mock('@/config', () => ({
|
|
ENABLE_WEBSITE_FIRECRAWL: true,
|
|
ENABLE_WEBSITE_JINAREADER: false,
|
|
ENABLE_WEBSITE_WATERCRAWL: false,
|
|
}))
|
|
|
|
// Mock dataset detail context
|
|
let mockDatasetDetail: DataSet | undefined
|
|
vi.mock('@/context/dataset-detail', () => ({
|
|
useDatasetDetailContextWithSelector: (selector: (state: { dataset: DataSet | undefined }) => DataSet | undefined) => {
|
|
return selector({ dataset: mockDatasetDetail })
|
|
},
|
|
}))
|
|
|
|
// Mock provider context
|
|
let mockPlan = {
|
|
type: Plan.professional,
|
|
usage: { vectorSpace: 50, buildApps: 0, documentsUploadQuota: 0, vectorStorageQuota: 0 },
|
|
total: { vectorSpace: 100, buildApps: 0, documentsUploadQuota: 0, vectorStorageQuota: 0 },
|
|
}
|
|
let mockEnableBilling = false
|
|
|
|
vi.mock('@/context/provider-context', () => ({
|
|
useProviderContext: () => ({
|
|
plan: mockPlan,
|
|
enableBilling: mockEnableBilling,
|
|
}),
|
|
}))
|
|
|
|
// Mock child components
|
|
vi.mock('../file-uploader', () => ({
|
|
default: ({ onPreview, fileList }: { onPreview: (file: File) => void, fileList: FileItem[] }) => (
|
|
<div data-testid="file-uploader">
|
|
<span data-testid="file-count">{fileList.length}</span>
|
|
<button data-testid="preview-file" onClick={() => onPreview(new File(['test'], 'test.txt'))}>
|
|
Preview
|
|
</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('../website', () => ({
|
|
default: ({ onPreview }: { onPreview: (item: CrawlResultItem) => void }) => (
|
|
<div data-testid="website">
|
|
<button
|
|
data-testid="preview-website"
|
|
onClick={() => onPreview({ title: 'Test', markdown: '', description: '', source_url: 'https://test.com' })}
|
|
>
|
|
Preview Website
|
|
</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('../empty-dataset-creation-modal', () => ({
|
|
default: ({ show, onHide }: { show: boolean, onHide: () => void }) => (
|
|
show
|
|
? (
|
|
<div data-testid="empty-dataset-modal">
|
|
<button data-testid="close-modal" onClick={onHide}>Close</button>
|
|
</div>
|
|
)
|
|
: null
|
|
),
|
|
}))
|
|
|
|
// NotionConnector is a base component - imported directly without mock
|
|
// It only depends on i18n which is globally mocked
|
|
|
|
vi.mock('@/app/components/base/notion-page-selector', () => ({
|
|
NotionPageSelector: ({ onPreview }: { onPreview: (page: NotionPage) => void }) => (
|
|
<div data-testid="notion-page-selector">
|
|
<button
|
|
data-testid="preview-notion"
|
|
onClick={() => onPreview({ page_id: 'page-1', type: 'page' } as NotionPage)}
|
|
>
|
|
Preview Notion
|
|
</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('@/app/components/billing/vector-space-full', () => ({
|
|
default: () => <div data-testid="vector-space-full">Vector Space Full</div>,
|
|
}))
|
|
|
|
vi.mock('@/app/components/billing/plan-upgrade-modal', () => ({
|
|
default: ({ show, onClose }: { show: boolean, onClose: () => void }) => (
|
|
show
|
|
? (
|
|
<div data-testid="plan-upgrade-modal">
|
|
<button data-testid="close-upgrade-modal" onClick={onClose}>Close</button>
|
|
</div>
|
|
)
|
|
: null
|
|
),
|
|
}))
|
|
|
|
vi.mock('../file-preview', () => ({
|
|
default: ({ file, hidePreview }: { file: File, hidePreview: () => void }) => (
|
|
<div data-testid="file-preview">
|
|
<span>{file.name}</span>
|
|
<button data-testid="hide-file-preview" onClick={hidePreview}>Hide</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
vi.mock('../notion-page-preview', () => ({
|
|
default: ({ currentPage, hidePreview }: { currentPage: NotionPage, hidePreview: () => void }) => (
|
|
<div data-testid="notion-page-preview">
|
|
<span>{currentPage.page_id}</span>
|
|
<button data-testid="hide-notion-preview" onClick={hidePreview}>Hide</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
// WebsitePreview is a sibling component without API dependencies - imported directly
|
|
// It only depends on i18n which is globally mocked
|
|
|
|
vi.mock('./upgrade-card', () => ({
|
|
default: () => <div data-testid="upgrade-card">Upgrade Card</div>,
|
|
}))
|
|
|
|
// ==========================================
|
|
// Test Data Builders
|
|
// ==========================================
|
|
|
|
const createMockCustomFile = (overrides: { id?: string, name?: string } = {}) => {
|
|
const file = new File(['test content'], overrides.name ?? 'test.txt', { type: 'text/plain' })
|
|
return Object.assign(file, {
|
|
id: overrides.id ?? 'uploaded-id',
|
|
extension: 'txt',
|
|
mime_type: 'text/plain',
|
|
created_by: 'user-1',
|
|
created_at: Date.now(),
|
|
})
|
|
}
|
|
|
|
const createMockFileItem = (overrides: Partial<FileItem> = {}): FileItem => ({
|
|
fileID: `file-${Date.now()}`,
|
|
file: createMockCustomFile(overrides.file as { id?: string, name?: string }),
|
|
progress: 100,
|
|
...overrides,
|
|
})
|
|
|
|
const createMockNotionPage = (overrides: Partial<NotionPage> = {}): NotionPage => ({
|
|
page_id: `page-${Date.now()}`,
|
|
type: 'page',
|
|
...overrides,
|
|
} as NotionPage)
|
|
|
|
const createMockCrawlResult = (overrides: Partial<CrawlResultItem> = {}): CrawlResultItem => ({
|
|
title: 'Test Page',
|
|
markdown: 'Test content',
|
|
description: 'Test description',
|
|
source_url: 'https://example.com',
|
|
...overrides,
|
|
})
|
|
|
|
const createMockDataSourceAuth = (overrides: Partial<DataSourceAuth> = {}): DataSourceAuth => ({
|
|
credential_id: 'cred-1',
|
|
provider: 'notion_datasource',
|
|
plugin_id: 'plugin-1',
|
|
credentials_list: [{ id: 'cred-1', name: 'Workspace 1' }],
|
|
...overrides,
|
|
} as DataSourceAuth)
|
|
|
|
const defaultProps = {
|
|
dataSourceType: DataSourceType.FILE,
|
|
dataSourceTypeDisable: false,
|
|
onSetting: vi.fn(),
|
|
files: [] as FileItem[],
|
|
updateFileList: vi.fn(),
|
|
updateFile: vi.fn(),
|
|
notionPages: [] as NotionPage[],
|
|
notionCredentialId: '',
|
|
updateNotionPages: vi.fn(),
|
|
updateNotionCredentialId: vi.fn(),
|
|
onStepChange: vi.fn(),
|
|
changeType: vi.fn(),
|
|
websitePages: [] as CrawlResultItem[],
|
|
updateWebsitePages: vi.fn(),
|
|
onWebsiteCrawlProviderChange: vi.fn(),
|
|
onWebsiteCrawlJobIdChange: vi.fn(),
|
|
crawlOptions: {
|
|
crawl_sub_pages: true,
|
|
only_main_content: true,
|
|
includes: '',
|
|
excludes: '',
|
|
limit: 10,
|
|
max_depth: '',
|
|
use_sitemap: true,
|
|
} as CrawlOptions,
|
|
onCrawlOptionsChange: vi.fn(),
|
|
authedDataSourceList: [] as DataSourceAuth[],
|
|
}
|
|
|
|
// ==========================================
|
|
// usePreviewState Hook Tests
|
|
// ==========================================
|
|
describe('usePreviewState Hook', () => {
|
|
// --------------------------------------------------------------------------
|
|
// Initial State Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Initial State', () => {
|
|
it('should initialize with all preview states undefined', () => {
|
|
// Arrange & Act
|
|
const { result } = renderHook(() => usePreviewState())
|
|
|
|
// Assert
|
|
expect(result.current.currentFile).toBeUndefined()
|
|
expect(result.current.currentNotionPage).toBeUndefined()
|
|
expect(result.current.currentWebsite).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// File Preview Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('File Preview', () => {
|
|
it('should show file preview when showFilePreview is called', () => {
|
|
// Arrange
|
|
const { result } = renderHook(() => usePreviewState())
|
|
const mockFile = new File(['test'], 'test.txt')
|
|
|
|
// Act
|
|
act(() => {
|
|
result.current.showFilePreview(mockFile)
|
|
})
|
|
|
|
// Assert
|
|
expect(result.current.currentFile).toBe(mockFile)
|
|
})
|
|
|
|
it('should hide file preview when hideFilePreview is called', () => {
|
|
// Arrange
|
|
const { result } = renderHook(() => usePreviewState())
|
|
const mockFile = new File(['test'], 'test.txt')
|
|
|
|
act(() => {
|
|
result.current.showFilePreview(mockFile)
|
|
})
|
|
|
|
// Act
|
|
act(() => {
|
|
result.current.hideFilePreview()
|
|
})
|
|
|
|
// Assert
|
|
expect(result.current.currentFile).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Notion Page Preview Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Notion Page Preview', () => {
|
|
it('should show notion page preview when showNotionPagePreview is called', () => {
|
|
// Arrange
|
|
const { result } = renderHook(() => usePreviewState())
|
|
const mockPage = createMockNotionPage()
|
|
|
|
// Act
|
|
act(() => {
|
|
result.current.showNotionPagePreview(mockPage)
|
|
})
|
|
|
|
// Assert
|
|
expect(result.current.currentNotionPage).toBe(mockPage)
|
|
})
|
|
|
|
it('should hide notion page preview when hideNotionPagePreview is called', () => {
|
|
// Arrange
|
|
const { result } = renderHook(() => usePreviewState())
|
|
const mockPage = createMockNotionPage()
|
|
|
|
act(() => {
|
|
result.current.showNotionPagePreview(mockPage)
|
|
})
|
|
|
|
// Act
|
|
act(() => {
|
|
result.current.hideNotionPagePreview()
|
|
})
|
|
|
|
// Assert
|
|
expect(result.current.currentNotionPage).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Website Preview Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Website Preview', () => {
|
|
it('should show website preview when showWebsitePreview is called', () => {
|
|
// Arrange
|
|
const { result } = renderHook(() => usePreviewState())
|
|
const mockWebsite = createMockCrawlResult()
|
|
|
|
// Act
|
|
act(() => {
|
|
result.current.showWebsitePreview(mockWebsite)
|
|
})
|
|
|
|
// Assert
|
|
expect(result.current.currentWebsite).toBe(mockWebsite)
|
|
})
|
|
|
|
it('should hide website preview when hideWebsitePreview is called', () => {
|
|
// Arrange
|
|
const { result } = renderHook(() => usePreviewState())
|
|
const mockWebsite = createMockCrawlResult()
|
|
|
|
act(() => {
|
|
result.current.showWebsitePreview(mockWebsite)
|
|
})
|
|
|
|
// Act
|
|
act(() => {
|
|
result.current.hideWebsitePreview()
|
|
})
|
|
|
|
// Assert
|
|
expect(result.current.currentWebsite).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Callback Stability Tests (Memoization)
|
|
// --------------------------------------------------------------------------
|
|
describe('Callback Stability', () => {
|
|
it('should maintain stable showFilePreview callback reference', () => {
|
|
// Arrange
|
|
const { result, rerender } = renderHook(() => usePreviewState())
|
|
const initialCallback = result.current.showFilePreview
|
|
|
|
// Act
|
|
rerender()
|
|
|
|
// Assert
|
|
expect(result.current.showFilePreview).toBe(initialCallback)
|
|
})
|
|
|
|
it('should maintain stable hideFilePreview callback reference', () => {
|
|
// Arrange
|
|
const { result, rerender } = renderHook(() => usePreviewState())
|
|
const initialCallback = result.current.hideFilePreview
|
|
|
|
// Act
|
|
rerender()
|
|
|
|
// Assert
|
|
expect(result.current.hideFilePreview).toBe(initialCallback)
|
|
})
|
|
|
|
it('should maintain stable showNotionPagePreview callback reference', () => {
|
|
// Arrange
|
|
const { result, rerender } = renderHook(() => usePreviewState())
|
|
const initialCallback = result.current.showNotionPagePreview
|
|
|
|
// Act
|
|
rerender()
|
|
|
|
// Assert
|
|
expect(result.current.showNotionPagePreview).toBe(initialCallback)
|
|
})
|
|
|
|
it('should maintain stable hideNotionPagePreview callback reference', () => {
|
|
// Arrange
|
|
const { result, rerender } = renderHook(() => usePreviewState())
|
|
const initialCallback = result.current.hideNotionPagePreview
|
|
|
|
// Act
|
|
rerender()
|
|
|
|
// Assert
|
|
expect(result.current.hideNotionPagePreview).toBe(initialCallback)
|
|
})
|
|
|
|
it('should maintain stable showWebsitePreview callback reference', () => {
|
|
// Arrange
|
|
const { result, rerender } = renderHook(() => usePreviewState())
|
|
const initialCallback = result.current.showWebsitePreview
|
|
|
|
// Act
|
|
rerender()
|
|
|
|
// Assert
|
|
expect(result.current.showWebsitePreview).toBe(initialCallback)
|
|
})
|
|
|
|
it('should maintain stable hideWebsitePreview callback reference', () => {
|
|
// Arrange
|
|
const { result, rerender } = renderHook(() => usePreviewState())
|
|
const initialCallback = result.current.hideWebsitePreview
|
|
|
|
// Act
|
|
rerender()
|
|
|
|
// Assert
|
|
expect(result.current.hideWebsitePreview).toBe(initialCallback)
|
|
})
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// DataSourceTypeSelector Component Tests
|
|
// ==========================================
|
|
describe('DataSourceTypeSelector', () => {
|
|
const defaultSelectorProps = {
|
|
currentType: DataSourceType.FILE,
|
|
disabled: false,
|
|
onChange: vi.fn(),
|
|
onClearPreviews: vi.fn(),
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Rendering Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Rendering', () => {
|
|
it('should render all data source options when web is enabled', () => {
|
|
// Arrange & Act
|
|
render(<DataSourceTypeSelector {...defaultSelectorProps} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.file')).toBeInTheDocument()
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.notion')).toBeInTheDocument()
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.web')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should highlight active type', () => {
|
|
// Arrange & Act
|
|
const { container } = render(
|
|
<DataSourceTypeSelector {...defaultSelectorProps} currentType={DataSourceType.NOTION} />,
|
|
)
|
|
|
|
// Assert - The active item should have the active class
|
|
const items = container.querySelectorAll('[class*="dataSourceItem"]')
|
|
expect(items.length).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// User Interactions Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('User Interactions', () => {
|
|
it('should call onChange when a type is clicked', () => {
|
|
// Arrange
|
|
const onChange = vi.fn()
|
|
render(<DataSourceTypeSelector {...defaultSelectorProps} onChange={onChange} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.notion'))
|
|
|
|
// Assert
|
|
expect(onChange).toHaveBeenCalledWith(DataSourceType.NOTION)
|
|
})
|
|
|
|
it('should call onClearPreviews when a type is clicked', () => {
|
|
// Arrange
|
|
const onClearPreviews = vi.fn()
|
|
render(<DataSourceTypeSelector {...defaultSelectorProps} onClearPreviews={onClearPreviews} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.web'))
|
|
|
|
// Assert
|
|
expect(onClearPreviews).toHaveBeenCalledWith(DataSourceType.WEB)
|
|
})
|
|
|
|
it('should not call onChange when disabled', () => {
|
|
// Arrange
|
|
const onChange = vi.fn()
|
|
render(<DataSourceTypeSelector {...defaultSelectorProps} disabled onChange={onChange} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.notion'))
|
|
|
|
// Assert
|
|
expect(onChange).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should not call onClearPreviews when disabled', () => {
|
|
// Arrange
|
|
const onClearPreviews = vi.fn()
|
|
render(<DataSourceTypeSelector {...defaultSelectorProps} disabled onClearPreviews={onClearPreviews} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.notion'))
|
|
|
|
// Assert
|
|
expect(onClearPreviews).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// NextStepButton Component Tests
|
|
// ==========================================
|
|
describe('NextStepButton', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Rendering Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Rendering', () => {
|
|
it('should render with correct label', () => {
|
|
// Arrange & Act
|
|
render(<NextStepButton disabled={false} onClick={vi.fn()} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('datasetCreation.stepOne.button')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render with arrow icon', () => {
|
|
// Arrange & Act
|
|
const { container } = render(<NextStepButton disabled={false} onClick={vi.fn()} />)
|
|
|
|
// Assert
|
|
const svgIcon = container.querySelector('svg')
|
|
expect(svgIcon).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Props Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Props', () => {
|
|
it('should be disabled when disabled prop is true', () => {
|
|
// Arrange & Act
|
|
render(<NextStepButton disabled onClick={vi.fn()} />)
|
|
|
|
// Assert
|
|
expect(screen.getByRole('button')).toBeDisabled()
|
|
})
|
|
|
|
it('should be enabled when disabled prop is false', () => {
|
|
// Arrange & Act
|
|
render(<NextStepButton disabled={false} onClick={vi.fn()} />)
|
|
|
|
// Assert
|
|
expect(screen.getByRole('button')).not.toBeDisabled()
|
|
})
|
|
|
|
it('should call onClick when clicked and not disabled', () => {
|
|
// Arrange
|
|
const onClick = vi.fn()
|
|
render(<NextStepButton disabled={false} onClick={onClick} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByRole('button'))
|
|
|
|
// Assert
|
|
expect(onClick).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should not call onClick when clicked and disabled', () => {
|
|
// Arrange
|
|
const onClick = vi.fn()
|
|
render(<NextStepButton disabled onClick={onClick} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByRole('button'))
|
|
|
|
// Assert
|
|
expect(onClick).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// PreviewPanel Component Tests
|
|
// ==========================================
|
|
describe('PreviewPanel', () => {
|
|
const defaultPreviewProps = {
|
|
currentFile: undefined as File | undefined,
|
|
currentNotionPage: undefined as NotionPage | undefined,
|
|
currentWebsite: undefined as CrawlResultItem | undefined,
|
|
notionCredentialId: 'cred-1',
|
|
isShowPlanUpgradeModal: false,
|
|
hideFilePreview: vi.fn(),
|
|
hideNotionPagePreview: vi.fn(),
|
|
hideWebsitePreview: vi.fn(),
|
|
hidePlanUpgradeModal: vi.fn(),
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Conditional Rendering Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Conditional Rendering', () => {
|
|
it('should not render FilePreview when currentFile is undefined', () => {
|
|
// Arrange & Act
|
|
render(<PreviewPanel {...defaultPreviewProps} />)
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('file-preview')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render FilePreview when currentFile is defined', () => {
|
|
// Arrange
|
|
const file = new File(['test'], 'test.txt')
|
|
|
|
// Act
|
|
render(<PreviewPanel {...defaultPreviewProps} currentFile={file} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('file-preview')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render NotionPagePreview when currentNotionPage is undefined', () => {
|
|
// Arrange & Act
|
|
render(<PreviewPanel {...defaultPreviewProps} />)
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('notion-page-preview')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render NotionPagePreview when currentNotionPage is defined', () => {
|
|
// Arrange
|
|
const page = createMockNotionPage()
|
|
|
|
// Act
|
|
render(<PreviewPanel {...defaultPreviewProps} currentNotionPage={page} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('notion-page-preview')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render WebsitePreview when currentWebsite is undefined', () => {
|
|
// Arrange & Act
|
|
render(<PreviewPanel {...defaultPreviewProps} />)
|
|
|
|
// Assert - pagePreview is the title shown in WebsitePreview
|
|
expect(screen.queryByText('datasetCreation.stepOne.pagePreview')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render WebsitePreview when currentWebsite is defined', () => {
|
|
// Arrange
|
|
const website = createMockCrawlResult()
|
|
|
|
// Act
|
|
render(<PreviewPanel {...defaultPreviewProps} currentWebsite={website} />)
|
|
|
|
// Assert - Check for the preview title and source URL
|
|
expect(screen.getByText('datasetCreation.stepOne.pagePreview')).toBeInTheDocument()
|
|
expect(screen.getByText(website.source_url)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render PlanUpgradeModal when isShowPlanUpgradeModal is false', () => {
|
|
// Arrange & Act
|
|
render(<PreviewPanel {...defaultPreviewProps} isShowPlanUpgradeModal={false} />)
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('plan-upgrade-modal')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render PlanUpgradeModal when isShowPlanUpgradeModal is true', () => {
|
|
// Arrange & Act
|
|
render(<PreviewPanel {...defaultPreviewProps} isShowPlanUpgradeModal />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Event Handler Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Event Handlers', () => {
|
|
it('should call hideFilePreview when file preview close is clicked', () => {
|
|
// Arrange
|
|
const hideFilePreview = vi.fn()
|
|
const file = new File(['test'], 'test.txt')
|
|
render(<PreviewPanel {...defaultPreviewProps} currentFile={file} hideFilePreview={hideFilePreview} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('hide-file-preview'))
|
|
|
|
// Assert
|
|
expect(hideFilePreview).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should call hideNotionPagePreview when notion preview close is clicked', () => {
|
|
// Arrange
|
|
const hideNotionPagePreview = vi.fn()
|
|
const page = createMockNotionPage()
|
|
render(<PreviewPanel {...defaultPreviewProps} currentNotionPage={page} hideNotionPagePreview={hideNotionPagePreview} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('hide-notion-preview'))
|
|
|
|
// Assert
|
|
expect(hideNotionPagePreview).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should call hideWebsitePreview when website preview close is clicked', () => {
|
|
// Arrange
|
|
const hideWebsitePreview = vi.fn()
|
|
const website = createMockCrawlResult()
|
|
const { container } = render(<PreviewPanel {...defaultPreviewProps} currentWebsite={website} hideWebsitePreview={hideWebsitePreview} />)
|
|
|
|
// Act - Find the close button (div with cursor-pointer class containing the XMarkIcon)
|
|
const closeButton = container.querySelector('.cursor-pointer')
|
|
expect(closeButton).toBeInTheDocument()
|
|
fireEvent.click(closeButton!)
|
|
|
|
// Assert
|
|
expect(hideWebsitePreview).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should call hidePlanUpgradeModal when modal close is clicked', () => {
|
|
// Arrange
|
|
const hidePlanUpgradeModal = vi.fn()
|
|
render(<PreviewPanel {...defaultPreviewProps} isShowPlanUpgradeModal hidePlanUpgradeModal={hidePlanUpgradeModal} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('close-upgrade-modal'))
|
|
|
|
// Assert
|
|
expect(hidePlanUpgradeModal).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// StepOne Component Tests
|
|
// ==========================================
|
|
describe('StepOne', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockDatasetDetail = undefined
|
|
mockPlan = {
|
|
type: Plan.professional,
|
|
usage: { vectorSpace: 50, buildApps: 0, documentsUploadQuota: 0, vectorStorageQuota: 0 },
|
|
total: { vectorSpace: 100, buildApps: 0, documentsUploadQuota: 0, vectorStorageQuota: 0 },
|
|
}
|
|
mockEnableBilling = false
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Rendering Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Rendering', () => {
|
|
it('should render without crashing', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('datasetCreation.steps.one')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render DataSourceTypeSelector when not editing existing dataset', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.file')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render FileUploader when dataSourceType is FILE', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.FILE} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('file-uploader')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render NotionConnector when dataSourceType is NOTION and not authenticated', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} />)
|
|
|
|
// Assert - NotionConnector shows sync title and connect button
|
|
expect(screen.getByText('datasetCreation.stepOne.notionSyncTitle')).toBeInTheDocument()
|
|
expect(screen.getByRole('button', { name: /datasetCreation.stepOne.connect/i })).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render NotionPageSelector when dataSourceType is NOTION and authenticated', () => {
|
|
// Arrange
|
|
const authedDataSourceList = [createMockDataSourceAuth()]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} authedDataSourceList={authedDataSourceList} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('notion-page-selector')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render Website when dataSourceType is WEB', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.WEB} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('website')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render empty dataset creation link when no datasetId', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('datasetCreation.stepOne.emptyDatasetCreation')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render empty dataset creation link when datasetId exists', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} datasetId="dataset-123" />)
|
|
|
|
// Assert
|
|
expect(screen.queryByText('datasetCreation.stepOne.emptyDatasetCreation')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Props Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Props', () => {
|
|
it('should pass files to FileUploader', () => {
|
|
// Arrange
|
|
const files = [createMockFileItem()]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} files={files} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('file-count')).toHaveTextContent('1')
|
|
})
|
|
|
|
it('should call onSetting when NotionConnector connect button is clicked', () => {
|
|
// Arrange
|
|
const onSetting = vi.fn()
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} onSetting={onSetting} />)
|
|
|
|
// Act - The NotionConnector's button calls onSetting
|
|
fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.connect/i }))
|
|
|
|
// Assert
|
|
expect(onSetting).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should call changeType when data source type is changed', () => {
|
|
// Arrange
|
|
const changeType = vi.fn()
|
|
render(<StepOne {...defaultProps} changeType={changeType} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.notion'))
|
|
|
|
// Assert
|
|
expect(changeType).toHaveBeenCalledWith(DataSourceType.NOTION)
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// State Management Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('State Management', () => {
|
|
it('should open empty dataset modal when link is clicked', () => {
|
|
// Arrange
|
|
render(<StepOne {...defaultProps} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.emptyDatasetCreation'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('empty-dataset-modal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should close empty dataset modal when close is clicked', () => {
|
|
// Arrange
|
|
render(<StepOne {...defaultProps} />)
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.emptyDatasetCreation'))
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('close-modal'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('empty-dataset-modal')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Memoization Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Memoization', () => {
|
|
it('should correctly compute isNotionAuthed based on authedDataSourceList', () => {
|
|
// Arrange - No auth
|
|
const { rerender } = render(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} />)
|
|
// NotionConnector shows the sync title when not authenticated
|
|
expect(screen.getByText('datasetCreation.stepOne.notionSyncTitle')).toBeInTheDocument()
|
|
|
|
// Act - Add auth
|
|
const authedDataSourceList = [createMockDataSourceAuth()]
|
|
rerender(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} authedDataSourceList={authedDataSourceList} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('notion-page-selector')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should correctly compute fileNextDisabled when files are empty', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} files={[]} />)
|
|
|
|
// Assert - Button should be disabled
|
|
expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).toBeDisabled()
|
|
})
|
|
|
|
it('should correctly compute fileNextDisabled when files are loaded', () => {
|
|
// Arrange
|
|
const files = [createMockFileItem()]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} files={files} />)
|
|
|
|
// Assert - Button should be enabled
|
|
expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).not.toBeDisabled()
|
|
})
|
|
|
|
it('should correctly compute fileNextDisabled when some files are not uploaded', () => {
|
|
// Arrange - Create a file item without id (not yet uploaded)
|
|
const file = new File(['test'], 'test.txt', { type: 'text/plain' })
|
|
const fileItem: FileItem = {
|
|
fileID: 'temp-id',
|
|
file: Object.assign(file, { id: undefined, extension: 'txt', mime_type: 'text/plain' }),
|
|
progress: 0,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} files={[fileItem]} />)
|
|
|
|
// Assert - Button should be disabled
|
|
expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).toBeDisabled()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Callback Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Callbacks', () => {
|
|
it('should call onStepChange when next button is clicked with valid files', () => {
|
|
// Arrange
|
|
const onStepChange = vi.fn()
|
|
const files = [createMockFileItem()]
|
|
render(<StepOne {...defaultProps} files={files} onStepChange={onStepChange} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }))
|
|
|
|
// Assert
|
|
expect(onStepChange).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should show plan upgrade modal when batch upload not supported and multiple files', () => {
|
|
// Arrange
|
|
mockEnableBilling = true
|
|
mockPlan.type = Plan.sandbox
|
|
const files = [createMockFileItem(), createMockFileItem()]
|
|
render(<StepOne {...defaultProps} files={files} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show upgrade card when in sandbox plan with files', () => {
|
|
// Arrange
|
|
mockEnableBilling = true
|
|
mockPlan.type = Plan.sandbox
|
|
const files = [createMockFileItem()]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} files={files} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('upgrade-card')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Vector Space Full Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Vector Space Full', () => {
|
|
it('should show VectorSpaceFull when vector space is full and billing is enabled', () => {
|
|
// Arrange
|
|
mockEnableBilling = true
|
|
mockPlan.usage.vectorSpace = 100
|
|
mockPlan.total.vectorSpace = 100
|
|
const files = [createMockFileItem()]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} files={files} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('vector-space-full')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should disable next button when vector space is full', () => {
|
|
// Arrange
|
|
mockEnableBilling = true
|
|
mockPlan.usage.vectorSpace = 100
|
|
mockPlan.total.vectorSpace = 100
|
|
const files = [createMockFileItem()]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} files={files} />)
|
|
|
|
// Assert
|
|
expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).toBeDisabled()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Preview Integration Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Preview Integration', () => {
|
|
it('should show file preview when file preview button is clicked', () => {
|
|
// Arrange
|
|
render(<StepOne {...defaultProps} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('preview-file'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('file-preview')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should hide file preview when hide button is clicked', () => {
|
|
// Arrange
|
|
render(<StepOne {...defaultProps} />)
|
|
fireEvent.click(screen.getByTestId('preview-file'))
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('hide-file-preview'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('file-preview')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should show notion page preview when preview button is clicked', () => {
|
|
// Arrange
|
|
const authedDataSourceList = [createMockDataSourceAuth()]
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} authedDataSourceList={authedDataSourceList} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('preview-notion'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('notion-page-preview')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show website preview when preview button is clicked', () => {
|
|
// Arrange
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.WEB} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('preview-website'))
|
|
|
|
// Assert - Check for pagePreview title which is shown by WebsitePreview
|
|
expect(screen.getByText('datasetCreation.stepOne.pagePreview')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Edge Cases
|
|
// --------------------------------------------------------------------------
|
|
describe('Edge Cases', () => {
|
|
it('should handle empty notionPages array', () => {
|
|
// Arrange
|
|
const authedDataSourceList = [createMockDataSourceAuth()]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} notionPages={[]} authedDataSourceList={authedDataSourceList} />)
|
|
|
|
// Assert - Button should be disabled when no pages selected
|
|
expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).toBeDisabled()
|
|
})
|
|
|
|
it('should handle empty websitePages array', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.WEB} websitePages={[]} />)
|
|
|
|
// Assert - Button should be disabled when no pages crawled
|
|
expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).toBeDisabled()
|
|
})
|
|
|
|
it('should handle empty authedDataSourceList', () => {
|
|
// Arrange & Act
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} authedDataSourceList={[]} />)
|
|
|
|
// Assert - Should show NotionConnector with connect button
|
|
expect(screen.getByText('datasetCreation.stepOne.notionSyncTitle')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle authedDataSourceList without notion credentials', () => {
|
|
// Arrange
|
|
const authedDataSourceList = [createMockDataSourceAuth({ credentials_list: [] })]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} dataSourceType={DataSourceType.NOTION} authedDataSourceList={authedDataSourceList} />)
|
|
|
|
// Assert - Should show NotionConnector with connect button
|
|
expect(screen.getByText('datasetCreation.stepOne.notionSyncTitle')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should clear previews when switching data source types', () => {
|
|
// Arrange
|
|
render(<StepOne {...defaultProps} />)
|
|
fireEvent.click(screen.getByTestId('preview-file'))
|
|
expect(screen.getByTestId('file-preview')).toBeInTheDocument()
|
|
|
|
// Act - Change to NOTION
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.notion'))
|
|
|
|
// Assert - File preview should be cleared
|
|
expect(screen.queryByTestId('file-preview')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Integration Tests
|
|
// --------------------------------------------------------------------------
|
|
describe('Integration', () => {
|
|
it('should complete file upload flow', () => {
|
|
// Arrange
|
|
const onStepChange = vi.fn()
|
|
const files = [createMockFileItem()]
|
|
|
|
// Act
|
|
render(<StepOne {...defaultProps} files={files} onStepChange={onStepChange} />)
|
|
fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }))
|
|
|
|
// Assert
|
|
expect(onStepChange).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should complete notion page selection flow', () => {
|
|
// Arrange
|
|
const onStepChange = vi.fn()
|
|
const authedDataSourceList = [createMockDataSourceAuth()]
|
|
const notionPages = [createMockNotionPage()]
|
|
|
|
// Act
|
|
render(
|
|
<StepOne
|
|
{...defaultProps}
|
|
dataSourceType={DataSourceType.NOTION}
|
|
authedDataSourceList={authedDataSourceList}
|
|
notionPages={notionPages}
|
|
onStepChange={onStepChange}
|
|
/>,
|
|
)
|
|
fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }))
|
|
|
|
// Assert
|
|
expect(onStepChange).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should complete website crawl flow', () => {
|
|
// Arrange
|
|
const onStepChange = vi.fn()
|
|
const websitePages = [createMockCrawlResult()]
|
|
|
|
// Act
|
|
render(
|
|
<StepOne
|
|
{...defaultProps}
|
|
dataSourceType={DataSourceType.WEB}
|
|
websitePages={websitePages}
|
|
onStepChange={onStepChange}
|
|
/>,
|
|
)
|
|
fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }))
|
|
|
|
// Assert
|
|
expect(onStepChange).toHaveBeenCalled()
|
|
})
|
|
})
|
|
})
|