mirror of https://github.com/langgenius/dify.git
1136 lines
37 KiB
TypeScript
1136 lines
37 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, FileItem } from '@/models/datasets'
|
|
import { fireEvent, render, screen } from '@testing-library/react'
|
|
import { Plan } from '@/app/components/billing/type'
|
|
import { CredentialTypeEnum } from '@/app/components/plugins/plugin-auth/types'
|
|
import { DataSourceType } from '@/models/datasets'
|
|
import StepOne from './index'
|
|
|
|
// ==========================================
|
|
// Mock External Dependencies
|
|
// ==========================================
|
|
|
|
vi.mock('react-i18next', () => ({
|
|
useTranslation: () => ({
|
|
t: (key: string) => key,
|
|
}),
|
|
}))
|
|
|
|
// Mock dataset detail context
|
|
let mockDatasetDetail: { data_source_type?: DataSourceType } | undefined
|
|
vi.mock('@/context/dataset-detail', () => ({
|
|
useDatasetDetailContextWithSelector: (selector: (state: { dataset: typeof mockDatasetDetail }) => unknown) => {
|
|
const state = { dataset: mockDatasetDetail }
|
|
return selector(state)
|
|
},
|
|
}))
|
|
|
|
// Mock provider context
|
|
let mockPlanType: Plan = Plan.sandbox
|
|
let mockEnableBilling = false
|
|
let mockVectorSpaceUsage = 1
|
|
let mockVectorSpaceTotal = 10
|
|
vi.mock('@/context/provider-context', () => ({
|
|
useProviderContext: () => ({
|
|
plan: {
|
|
type: mockPlanType,
|
|
usage: { vectorSpace: mockVectorSpaceUsage },
|
|
total: { vectorSpace: mockVectorSpaceTotal },
|
|
},
|
|
enableBilling: mockEnableBilling,
|
|
}),
|
|
}))
|
|
|
|
// Mock config for web crawl features (required by DataSourceSelector)
|
|
vi.mock('@/config', () => ({
|
|
ENABLE_WEBSITE_FIRECRAWL: true,
|
|
ENABLE_WEBSITE_JINAREADER: true,
|
|
ENABLE_WEBSITE_WATERCRAWL: false,
|
|
}))
|
|
|
|
// ==========================================
|
|
// Mock Child Components with API Dependencies
|
|
// ==========================================
|
|
|
|
// Track props passed to child components for verification
|
|
let fileSourceProps: Record<string, unknown> = {}
|
|
let notionSourceProps: Record<string, unknown> = {}
|
|
let webSourceProps: Record<string, unknown> = {}
|
|
|
|
// Mock sources components (they have complex dependencies: FileUploader, NotionPageSelector, Website)
|
|
vi.mock('./sources', () => ({
|
|
FileSource: (props: Record<string, unknown>) => {
|
|
fileSourceProps = props
|
|
return (
|
|
<div data-testid="file-source">
|
|
<button data-testid="file-source-next" onClick={props.onStepChange as () => void}>Next</button>
|
|
<button data-testid="file-source-preview" onClick={() => (props.onPreview as (f: File) => void)(new File([''], 'test.txt'))}>Preview File</button>
|
|
</div>
|
|
)
|
|
},
|
|
NotionSource: (props: Record<string, unknown>) => {
|
|
notionSourceProps = props
|
|
return (
|
|
<div data-testid="notion-source">
|
|
<button data-testid="notion-source-next" onClick={props.onStepChange as () => void}>Next</button>
|
|
<button data-testid="notion-source-preview" onClick={() => (props.onPreview as (p: NotionPage) => void)({ page_id: 'page-1', page_name: 'Test Page', parent_id: '', type: 'page', is_bound: false, page_icon: null, workspace_id: 'ws-1' })}>Preview Notion</button>
|
|
</div>
|
|
)
|
|
},
|
|
WebSource: (props: Record<string, unknown>) => {
|
|
webSourceProps = props
|
|
return (
|
|
<div data-testid="web-source">
|
|
<button data-testid="web-source-next" onClick={props.onStepChange as () => void}>Next</button>
|
|
<button data-testid="web-source-preview" onClick={() => (props.onPreview as (w: CrawlResultItem) => void)({ title: 'Test', markdown: '', description: '', source_url: 'https://test.com' })}>Preview Website</button>
|
|
</div>
|
|
)
|
|
},
|
|
}))
|
|
|
|
// NOTE: DataSourceSelector is imported directly (no API dependencies)
|
|
// NOTE: WebsitePreview is imported directly (no API dependencies)
|
|
|
|
// Mock components with API service dependencies
|
|
vi.mock('../empty-dataset-creation-modal', () => ({
|
|
__esModule: true,
|
|
default: ({ show, onHide }: { show: boolean, onHide: () => void }) => (
|
|
show ? <div data-testid="empty-dataset-modal"><button onClick={onHide}>Close</button></div> : null
|
|
),
|
|
}))
|
|
|
|
// Mock FilePreview (depends on fetchFilePreview service)
|
|
vi.mock('../file-preview', () => ({
|
|
__esModule: true,
|
|
default: ({ file, hidePreview }: { file: File, hidePreview: () => void }) => (
|
|
<div data-testid="file-preview">
|
|
<span data-testid="file-preview-name">{file.name}</span>
|
|
<button data-testid="file-preview-close" onClick={hidePreview}>Close</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
// Mock NotionPagePreview (depends on fetchNotionPagePreview service)
|
|
vi.mock('../notion-page-preview', () => ({
|
|
__esModule: true,
|
|
default: ({ currentPage, hidePreview }: { currentPage: NotionPage, hidePreview: () => void }) => (
|
|
<div data-testid="notion-preview">
|
|
<span data-testid="notion-preview-id">{currentPage.page_id}</span>
|
|
<button data-testid="notion-preview-close" onClick={hidePreview}>Close</button>
|
|
</div>
|
|
),
|
|
}))
|
|
|
|
// Mock PlanUpgradeModal (depends on useModalContext)
|
|
vi.mock('@/app/components/billing/plan-upgrade-modal', () => ({
|
|
__esModule: true,
|
|
default: ({ show, onClose }: { show: boolean, onClose: () => void }) => (
|
|
show ? <div data-testid="plan-upgrade-modal"><button data-testid="plan-upgrade-close" onClick={onClose}>Close</button></div> : null
|
|
),
|
|
}))
|
|
|
|
// ==========================================
|
|
// Test Data Factories
|
|
// ==========================================
|
|
|
|
const createMockFile = (overrides: Partial<FileItem> = {}): FileItem => ({
|
|
fileID: 'file-1',
|
|
file: Object.assign(new File(['content'], 'test.txt', { type: 'text/plain' }), { id: 'uploaded-id-1' }),
|
|
progress: 100,
|
|
...overrides,
|
|
})
|
|
|
|
const createMockNotionPage = (overrides: Partial<NotionPage> = {}): NotionPage => ({
|
|
page_id: 'page-1',
|
|
page_name: 'Test Page',
|
|
parent_id: 'parent-1',
|
|
type: 'page',
|
|
is_bound: false,
|
|
page_icon: null,
|
|
workspace_id: 'workspace-1',
|
|
...overrides,
|
|
})
|
|
|
|
const createMockCrawlResultItem = (overrides: Partial<CrawlResultItem> = {}): CrawlResultItem => ({
|
|
title: 'Test Website',
|
|
markdown: '# Test Content',
|
|
description: 'Test description',
|
|
source_url: 'https://example.com',
|
|
...overrides,
|
|
})
|
|
|
|
const createMockCrawlOptions = (overrides: Partial<CrawlOptions> = {}): CrawlOptions => ({
|
|
crawl_sub_pages: true,
|
|
only_main_content: true,
|
|
includes: '',
|
|
excludes: '',
|
|
limit: 10,
|
|
max_depth: '',
|
|
use_sitemap: true,
|
|
...overrides,
|
|
})
|
|
|
|
const createMockDataSourceAuth = (overrides: Partial<DataSourceAuth> = {}): DataSourceAuth => ({
|
|
author: 'test-author',
|
|
provider: 'notion_datasource',
|
|
plugin_id: 'notion-plugin',
|
|
plugin_unique_identifier: 'notion-plugin-unique',
|
|
icon: 'notion-icon',
|
|
name: 'Notion',
|
|
label: { en_US: 'Notion', zh_Hans: 'Notion' },
|
|
description: { en_US: 'Notion integration', zh_Hans: 'Notion集成' },
|
|
credentials_list: [{
|
|
id: 'cred-1',
|
|
type: CredentialTypeEnum.API_KEY,
|
|
name: 'Test Workspace',
|
|
credential: { workspace_id: 'ws-1' },
|
|
avatar_url: '',
|
|
is_default: false,
|
|
}],
|
|
...overrides,
|
|
})
|
|
|
|
const createDefaultProps = () => ({
|
|
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: createMockCrawlOptions(),
|
|
onCrawlOptionsChange: vi.fn(),
|
|
authedDataSourceList: [] as DataSourceAuth[],
|
|
})
|
|
|
|
// ==========================================
|
|
// Tests
|
|
// ==========================================
|
|
|
|
describe('StepOne', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
// Reset mock states
|
|
mockDatasetDetail = undefined
|
|
mockPlanType = Plan.sandbox
|
|
mockEnableBilling = false
|
|
mockVectorSpaceUsage = 1
|
|
mockVectorSpaceTotal = 10
|
|
// Reset captured props
|
|
fileSourceProps = {}
|
|
notionSourceProps = {}
|
|
webSourceProps = {}
|
|
})
|
|
|
|
// ==========================================
|
|
// Rendering Tests
|
|
// ==========================================
|
|
describe('Rendering', () => {
|
|
it('should render without crashing', () => {
|
|
// Arrange
|
|
const props = createDefaultProps()
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert - Real DataSourceSelector renders data source type options
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.file')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render step header when in create mode', () => {
|
|
// Arrange
|
|
const props = createDefaultProps()
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('datasetCreation.steps.one')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render data source selector when creating new dataset', () => {
|
|
// Arrange
|
|
const props = createDefaultProps()
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert - Real DataSourceSelector renders data source type options
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.file')).toBeInTheDocument()
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.notion')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render empty dataset creation link when no datasetId', () => {
|
|
// Arrange
|
|
const props = createDefaultProps()
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('datasetCreation.stepOne.emptyDatasetCreation')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render empty dataset creation link when datasetId exists', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), datasetId: 'dataset-123' }
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.queryByText('datasetCreation.stepOne.emptyDatasetCreation')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render data source selector when editing existing dataset with data_source_type', () => {
|
|
// Arrange
|
|
mockDatasetDetail = { data_source_type: DataSourceType.FILE }
|
|
const props = { ...createDefaultProps(), datasetId: 'dataset-123' }
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert - Data source type options should not be present
|
|
expect(screen.queryByText('datasetCreation.stepOne.dataSourceType.file')).not.toBeInTheDocument()
|
|
expect(screen.queryByText('datasetCreation.stepOne.dataSourceType.notion')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Data Source Type Rendering Tests
|
|
// ==========================================
|
|
describe('Data Source Type Rendering', () => {
|
|
it('should render FileSource when dataSourceType is FILE', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.FILE }
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('file-source')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('notion-source')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('web-source')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render NotionSource when dataSourceType is NOTION', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.NOTION }
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('notion-source')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('file-source')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('web-source')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render WebSource when dataSourceType is WEB', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.WEB }
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('web-source')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('file-source')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('notion-source')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should render nothing when dataSourceType is undefined', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: undefined }
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('file-source')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('notion-source')).not.toBeInTheDocument()
|
|
expect(screen.queryByTestId('web-source')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Props Passing Tests
|
|
// ==========================================
|
|
describe('Props Passing', () => {
|
|
it('should pass correct props to FileSource', () => {
|
|
// Arrange
|
|
const mockFiles = [createMockFile()]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(fileSourceProps.files).toBe(mockFiles)
|
|
expect(fileSourceProps.updateFileList).toBe(props.updateFileList)
|
|
expect(fileSourceProps.updateFile).toBe(props.updateFile)
|
|
expect(fileSourceProps.shouldShowDataSourceTypeList).toBe(true)
|
|
expect(typeof fileSourceProps.onStepChange).toBe('function')
|
|
expect(typeof fileSourceProps.onPreview).toBe('function')
|
|
})
|
|
|
|
it('should pass correct props to NotionSource', () => {
|
|
// Arrange
|
|
const mockNotionPages = [createMockNotionPage()]
|
|
const mockAuth = createMockDataSourceAuth()
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
notionPages: mockNotionPages,
|
|
notionCredentialId: 'cred-123',
|
|
authedDataSourceList: [mockAuth],
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.notionPages).toBe(mockNotionPages)
|
|
expect(notionSourceProps.notionCredentialId).toBe('cred-123')
|
|
expect(notionSourceProps.isNotionAuthed).toBe(true)
|
|
expect(notionSourceProps.notionCredentialList).toEqual(mockAuth.credentials_list)
|
|
})
|
|
|
|
it('should pass correct props to WebSource', () => {
|
|
// Arrange
|
|
const mockWebsitePages = [createMockCrawlResultItem()]
|
|
const mockCrawlOptions = createMockCrawlOptions({ limit: 20 })
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.WEB,
|
|
websitePages: mockWebsitePages,
|
|
crawlOptions: mockCrawlOptions,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(webSourceProps.websitePages).toBe(mockWebsitePages)
|
|
expect(webSourceProps.crawlOptions).toBe(mockCrawlOptions)
|
|
expect(webSourceProps.shouldShowDataSourceTypeList).toBe(true)
|
|
})
|
|
|
|
it('should render DataSourceSelector with correct active state', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
dataSourceTypeDisable: false,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert - DataSourceSelector is now a real component
|
|
// It should render the file option with active styling
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.file')).toBeInTheDocument()
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.notion')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// State Management Tests
|
|
// ==========================================
|
|
describe('State Management', () => {
|
|
it('should show empty dataset modal when clicking empty dataset creation link', () => {
|
|
// Arrange
|
|
const props = createDefaultProps()
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.emptyDatasetCreation'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('empty-dataset-modal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should hide empty dataset modal when closing', () => {
|
|
// Arrange
|
|
const props = createDefaultProps()
|
|
render(<StepOne {...props} />)
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.emptyDatasetCreation'))
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('Close'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('empty-dataset-modal')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Preview State Tests
|
|
// ==========================================
|
|
describe('Preview State', () => {
|
|
it('should show file preview when file is selected', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.FILE }
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('file-source-preview'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('file-preview')).toBeInTheDocument()
|
|
expect(screen.getByTestId('file-preview-name')).toHaveTextContent('test.txt')
|
|
})
|
|
|
|
it('should hide file preview when close button is clicked', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.FILE }
|
|
render(<StepOne {...props} />)
|
|
fireEvent.click(screen.getByTestId('file-source-preview'))
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('file-preview-close'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('file-preview')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should show notion preview when notion page is selected', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.NOTION }
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('notion-source-preview'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('notion-preview')).toBeInTheDocument()
|
|
expect(screen.getByTestId('notion-preview-id')).toHaveTextContent('page-1')
|
|
})
|
|
|
|
it('should hide notion preview when close button is clicked', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.NOTION }
|
|
render(<StepOne {...props} />)
|
|
fireEvent.click(screen.getByTestId('notion-source-preview'))
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('notion-preview-close'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('notion-preview')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should show website preview when website is selected', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.WEB }
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('web-source-preview'))
|
|
|
|
// Assert - Real WebsitePreview shows the page preview title and URL
|
|
expect(screen.getByText('datasetCreation.stepOne.pagePreview')).toBeInTheDocument()
|
|
expect(screen.getByText('https://test.com')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should hide website preview when close button is clicked', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.WEB }
|
|
render(<StepOne {...props} />)
|
|
fireEvent.click(screen.getByTestId('web-source-preview'))
|
|
|
|
// Act - Click on the close icon (XMarkIcon) in the real WebsitePreview
|
|
const closeButton = screen.getByText('datasetCreation.stepOne.pagePreview').parentElement?.querySelector('svg')
|
|
if (closeButton)
|
|
fireEvent.click(closeButton.parentElement!)
|
|
|
|
// Assert
|
|
expect(screen.queryByText('https://test.com')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should hide previews when switching data source type', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.FILE }
|
|
render(<StepOne {...props} />)
|
|
fireEvent.click(screen.getByTestId('file-source-preview'))
|
|
expect(screen.getByTestId('file-preview')).toBeInTheDocument()
|
|
|
|
// Act - Switch to Notion (click on the real DataSourceSelector component)
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.notion'))
|
|
|
|
// Assert - File preview should be hidden (via the onHideFilePreview callback)
|
|
expect(screen.queryByTestId('file-preview')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Billing and Plan Upgrade Tests
|
|
// ==========================================
|
|
describe('Billing and Plan Upgrade', () => {
|
|
it('should show plan upgrade modal when sandbox user tries to upload multiple files', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.sandbox
|
|
mockEnableBilling = true
|
|
const mockFiles = [createMockFile({ fileID: 'file-1' }), createMockFile({ fileID: 'file-2' })]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('file-source-next'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
|
|
expect(props.onStepChange).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should show plan upgrade modal when sandbox user tries to add multiple notion pages', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.sandbox
|
|
mockEnableBilling = true
|
|
const mockNotionPages = [createMockNotionPage({ page_id: 'page-1' }), createMockNotionPage({ page_id: 'page-2' })]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
notionPages: mockNotionPages,
|
|
}
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('notion-source-next'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should show plan upgrade modal when sandbox user tries to add multiple website pages', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.sandbox
|
|
mockEnableBilling = true
|
|
const mockWebsitePages = [createMockCrawlResultItem({ source_url: 'https://a.com' }), createMockCrawlResultItem({ source_url: 'https://b.com' })]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.WEB,
|
|
websitePages: mockWebsitePages,
|
|
}
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('web-source-next'))
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('plan-upgrade-modal')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should proceed to next step when professional plan user uploads multiple files', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.professional
|
|
mockEnableBilling = true
|
|
const mockFiles = [createMockFile({ fileID: 'file-1' }), createMockFile({ fileID: 'file-2' })]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('file-source-next'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('plan-upgrade-modal')).not.toBeInTheDocument()
|
|
expect(props.onStepChange).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should proceed to next step when billing is disabled', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.sandbox
|
|
mockEnableBilling = false
|
|
const mockFiles = [createMockFile({ fileID: 'file-1' }), createMockFile({ fileID: 'file-2' })]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('file-source-next'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('plan-upgrade-modal')).not.toBeInTheDocument()
|
|
expect(props.onStepChange).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should proceed to next step with single file on sandbox plan', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.sandbox
|
|
mockEnableBilling = true
|
|
const mockFiles = [createMockFile({ fileID: 'file-1' })]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('file-source-next'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('plan-upgrade-modal')).not.toBeInTheDocument()
|
|
expect(props.onStepChange).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('should close plan upgrade modal when close button is clicked', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.sandbox
|
|
mockEnableBilling = true
|
|
const mockFiles = [createMockFile({ fileID: 'file-1' }), createMockFile({ fileID: 'file-2' })]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
render(<StepOne {...props} />)
|
|
fireEvent.click(screen.getByTestId('file-source-next'))
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('plan-upgrade-close'))
|
|
|
|
// Assert
|
|
expect(screen.queryByTestId('plan-upgrade-modal')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Memoization Logic Tests
|
|
// ==========================================
|
|
describe('Memoization Logic', () => {
|
|
it('should correctly compute isNotionAuthed when notion credentials exist', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
authedDataSourceList: [createMockDataSourceAuth()],
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.isNotionAuthed).toBe(true)
|
|
})
|
|
|
|
it('should correctly compute isNotionAuthed as false when no notion credentials', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
authedDataSourceList: [],
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.isNotionAuthed).toBe(false)
|
|
})
|
|
|
|
it('should correctly compute isNotionAuthed as false when credentials list is empty', () => {
|
|
// Arrange
|
|
const mockAuth = createMockDataSourceAuth({ credentials_list: [] })
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
authedDataSourceList: [mockAuth],
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.isNotionAuthed).toBe(false)
|
|
})
|
|
|
|
it('should correctly compute notionCredentialList', () => {
|
|
// Arrange
|
|
const mockAuth = createMockDataSourceAuth()
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
authedDataSourceList: [mockAuth],
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.notionCredentialList).toEqual(mockAuth.credentials_list)
|
|
})
|
|
|
|
it('should compute isShowVectorSpaceFull correctly when vector space is full', () => {
|
|
// Arrange
|
|
mockEnableBilling = true
|
|
mockVectorSpaceUsage = 10
|
|
mockVectorSpaceTotal = 10
|
|
const mockFiles = [createMockFile()]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(fileSourceProps.isShowVectorSpaceFull).toBe(true)
|
|
})
|
|
|
|
it('should compute isShowVectorSpaceFull as false when billing is disabled', () => {
|
|
// Arrange
|
|
mockEnableBilling = false
|
|
mockVectorSpaceUsage = 10
|
|
mockVectorSpaceTotal = 10
|
|
const mockFiles = [createMockFile()]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(fileSourceProps.isShowVectorSpaceFull).toBe(false)
|
|
})
|
|
|
|
it('should compute isShowVectorSpaceFull as false when no files loaded', () => {
|
|
// Arrange
|
|
mockEnableBilling = true
|
|
mockVectorSpaceUsage = 10
|
|
mockVectorSpaceTotal = 10
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: [],
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(fileSourceProps.isShowVectorSpaceFull).toBe(false)
|
|
})
|
|
|
|
it('should compute supportBatchUpload correctly for sandbox plan', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.sandbox
|
|
mockEnableBilling = true
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(fileSourceProps.supportBatchUpload).toBe(false)
|
|
})
|
|
|
|
it('should compute supportBatchUpload correctly for professional plan', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.professional
|
|
mockEnableBilling = true
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(fileSourceProps.supportBatchUpload).toBe(true)
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Callback Stability Tests
|
|
// ==========================================
|
|
describe('Callback Stability', () => {
|
|
it('should call changeType when data source type changes', () => {
|
|
// Arrange
|
|
const props = createDefaultProps()
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act - Click on Notion option in real DataSourceSelector
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.notion'))
|
|
|
|
// Assert
|
|
expect(props.changeType).toHaveBeenCalledWith(DataSourceType.NOTION)
|
|
})
|
|
|
|
it('should call onStepChange when proceeding to next step', () => {
|
|
// Arrange
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.FILE }
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByTestId('file-source-next'))
|
|
|
|
// Assert
|
|
expect(props.onStepChange).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Edge Cases Tests
|
|
// ==========================================
|
|
describe('Edge Cases', () => {
|
|
it('should handle empty authedDataSourceList', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
authedDataSourceList: [],
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.isNotionAuthed).toBe(false)
|
|
expect(notionSourceProps.notionCredentialList).toEqual([])
|
|
})
|
|
|
|
it('should handle undefined notionPages with default empty array', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
notionPages: undefined,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.notionPages).toEqual([])
|
|
})
|
|
|
|
it('should handle undefined websitePages with default empty array', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.WEB,
|
|
websitePages: undefined,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(webSourceProps.websitePages).toEqual([])
|
|
})
|
|
|
|
it('should handle files without uploaded id (not all files loaded)', () => {
|
|
// Arrange
|
|
mockEnableBilling = true
|
|
mockVectorSpaceUsage = 10
|
|
mockVectorSpaceTotal = 10
|
|
const mockFiles = [
|
|
{ fileID: 'file-1', file: new File([''], 'test.txt') as FileItem['file'], progress: 50 },
|
|
]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
files: mockFiles,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert - isShowVectorSpaceFull should be false because not all files are loaded
|
|
expect(fileSourceProps.isShowVectorSpaceFull).toBe(false)
|
|
})
|
|
|
|
it('should use dataset data_source_type when editing existing dataset', () => {
|
|
// Arrange
|
|
mockDatasetDetail = { data_source_type: DataSourceType.NOTION }
|
|
const props = {
|
|
...createDefaultProps(),
|
|
datasetId: 'dataset-123',
|
|
dataSourceType: DataSourceType.FILE, // This should be overridden
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(screen.getByTestId('notion-source')).toBeInTheDocument()
|
|
expect(screen.queryByTestId('file-source')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should show data source selector when editing dataset without data_source_type', () => {
|
|
// Arrange
|
|
mockDatasetDetail = { data_source_type: undefined }
|
|
const props = {
|
|
...createDefaultProps(),
|
|
datasetId: 'dataset-123',
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert - Real DataSourceSelector renders data source type options
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.file')).toBeInTheDocument()
|
|
expect(screen.getByText('datasetCreation.stepOne.dataSourceType.notion')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Event Handler Tests
|
|
// ==========================================
|
|
describe('Event Handlers', () => {
|
|
it('should handle data source type change correctly', () => {
|
|
// Arrange
|
|
const props = createDefaultProps()
|
|
render(<StepOne {...props} />)
|
|
|
|
// Act - Click on Web option in real DataSourceSelector
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.web'))
|
|
|
|
// Assert
|
|
expect(props.changeType).toHaveBeenCalledWith(DataSourceType.WEB)
|
|
})
|
|
|
|
it('should pass correct onSetting callback to NotionSource', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.onSetting).toBe(props.onSetting)
|
|
})
|
|
|
|
it('should pass callback handlers to WebSource', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.WEB,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(webSourceProps.updateWebsitePages).toBe(props.updateWebsitePages)
|
|
expect(webSourceProps.onWebsiteCrawlProviderChange).toBe(props.onWebsiteCrawlProviderChange)
|
|
expect(webSourceProps.onWebsiteCrawlJobIdChange).toBe(props.onWebsiteCrawlJobIdChange)
|
|
expect(webSourceProps.onCrawlOptionsChange).toBe(props.onCrawlOptionsChange)
|
|
})
|
|
})
|
|
|
|
// ==========================================
|
|
// Prop Variations Tests
|
|
// ==========================================
|
|
describe('Prop Variations', () => {
|
|
it('should pass isSandboxPlan correctly to FileSource', () => {
|
|
// Arrange
|
|
mockPlanType = Plan.sandbox
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.FILE }
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(fileSourceProps.isSandboxPlan).toBe(true)
|
|
})
|
|
|
|
it('should pass enableBilling correctly to FileSource', () => {
|
|
// Arrange
|
|
mockEnableBilling = true
|
|
const props = { ...createDefaultProps(), dataSourceType: DataSourceType.FILE }
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(fileSourceProps.enableBilling).toBe(true)
|
|
})
|
|
|
|
it('should pass datasetId to NotionSource', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.NOTION,
|
|
datasetId: 'dataset-abc',
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(notionSourceProps.datasetId).toBe('dataset-abc')
|
|
})
|
|
|
|
it('should pass authedDataSourceList to WebSource', () => {
|
|
// Arrange
|
|
const mockAuthedList = [createMockDataSourceAuth()]
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.WEB,
|
|
authedDataSourceList: mockAuthedList,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert
|
|
expect(webSourceProps.authedDataSourceList).toBe(mockAuthedList)
|
|
})
|
|
|
|
it('should correctly disable data source selector via dataSourceTypeDisable prop', () => {
|
|
// Arrange
|
|
const props = {
|
|
...createDefaultProps(),
|
|
dataSourceType: DataSourceType.FILE,
|
|
dataSourceTypeDisable: true,
|
|
}
|
|
|
|
// Act
|
|
render(<StepOne {...props} />)
|
|
|
|
// Assert - When disabled, clicking should not change type
|
|
fireEvent.click(screen.getByText('datasetCreation.stepOne.dataSourceType.notion'))
|
|
expect(props.changeType).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
})
|