dify/web/app/components/header/account-setting/data-source-page/data-source-website/config-jina-reader-modal.spec.tsx
akashseth-ifp 7dcf94f48f
test: remaining header component and increase branch coverage (#33052)
Co-authored-by: sahil <sahil@infocusp.com>
2026-03-09 09:18:11 +08:00

180 lines
6.9 KiB
TypeScript

import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { DataSourceProvider } from '@/models/common'
import { createDataSourceApiKeyBinding } from '@/service/datasets'
import ConfigJinaReaderModal from './config-jina-reader-modal'
/**
* ConfigJinaReaderModal Component Tests
* Tests validation, save logic, and basic rendering for the Jina Reader configuration modal.
*/
vi.mock('@/service/datasets', () => ({
createDataSourceApiKeyBinding: vi.fn(),
}))
describe('ConfigJinaReaderModal Component', () => {
const mockOnCancel = vi.fn()
const mockOnSaved = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
describe('Initial Rendering', () => {
it('should render the modal with API Key field and buttons', () => {
// Act
render(<ConfigJinaReaderModal onCancel={mockOnCancel} onSaved={mockOnSaved} />)
// Assert
expect(screen.getByText('datasetCreation.jinaReader.configJinaReader')).toBeInTheDocument()
expect(screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder')).toBeInTheDocument()
expect(screen.getByRole('button', { name: /common\.operation\.save/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /common\.operation\.cancel/i })).toBeInTheDocument()
expect(screen.getByRole('link', { name: /datasetCreation\.jinaReader\.getApiKeyLinkText/i })).toHaveAttribute('href', 'https://jina.ai/reader/')
})
})
describe('Form Interactions', () => {
it('should update state when API Key field changes', async () => {
const user = userEvent.setup()
// Arrange
render(<ConfigJinaReaderModal onCancel={mockOnCancel} onSaved={mockOnSaved} />)
const apiKeyInput = screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder')
// Act
await user.type(apiKeyInput, 'jina-test-key')
// Assert
expect(apiKeyInput).toHaveValue('jina-test-key')
})
it('should call onCancel when cancel button is clicked', async () => {
const user = userEvent.setup()
// Arrange
render(<ConfigJinaReaderModal onCancel={mockOnCancel} onSaved={mockOnSaved} />)
// Act
await user.click(screen.getByRole('button', { name: /common\.operation\.cancel/i }))
// Assert
expect(mockOnCancel).toHaveBeenCalled()
})
})
describe('Validation', () => {
it('should show error when saving without API Key', async () => {
const user = userEvent.setup()
// Arrange
render(<ConfigJinaReaderModal onCancel={mockOnCancel} onSaved={mockOnSaved} />)
// Act
await user.click(screen.getByRole('button', { name: /common\.operation\.save/i }))
// Assert
await waitFor(() => {
expect(screen.getByText('common.errorMsg.fieldRequired:{"field":"API Key"}')).toBeInTheDocument()
})
expect(createDataSourceApiKeyBinding).not.toHaveBeenCalled()
})
})
describe('Saving Logic', () => {
it('should save successfully with valid API Key', async () => {
const user = userEvent.setup()
// Arrange
vi.mocked(createDataSourceApiKeyBinding).mockResolvedValue({ result: 'success' })
render(<ConfigJinaReaderModal onCancel={mockOnCancel} onSaved={mockOnSaved} />)
const apiKeyInput = screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder')
// Act
await user.type(apiKeyInput, 'valid-jina-key')
await user.click(screen.getByRole('button', { name: /common\.operation\.save/i }))
// Assert
await waitFor(() => {
expect(createDataSourceApiKeyBinding).toHaveBeenCalledWith({
category: 'website',
provider: DataSourceProvider.jinaReader,
credentials: {
auth_type: 'bearer',
config: {
api_key: 'valid-jina-key',
},
},
})
})
await waitFor(() => {
expect(screen.getByText('common.api.success')).toBeInTheDocument()
expect(mockOnSaved).toHaveBeenCalled()
})
})
it('should ignore multiple save clicks while saving is in progress', async () => {
const user = userEvent.setup()
// Arrange
let resolveSave: (value: { result: 'success' }) => void
const savePromise = new Promise<{ result: 'success' }>((resolve) => {
resolveSave = resolve
})
vi.mocked(createDataSourceApiKeyBinding).mockReturnValue(savePromise)
render(<ConfigJinaReaderModal onCancel={mockOnCancel} onSaved={mockOnSaved} />)
await user.type(screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder'), 'test-key')
const saveBtn = screen.getByRole('button', { name: /common\.operation\.save/i })
// Act
await user.click(saveBtn)
await user.click(saveBtn)
// Assert
expect(createDataSourceApiKeyBinding).toHaveBeenCalledTimes(1)
// Cleanup
resolveSave!({ result: 'success' })
await waitFor(() => expect(mockOnSaved).toHaveBeenCalledTimes(1))
})
it('should show encryption info and external link in the modal', async () => {
render(<ConfigJinaReaderModal onCancel={mockOnCancel} onSaved={mockOnSaved} />)
// Verify PKCS1_OAEP link exists
const pkcsLink = screen.getByText('PKCS1_OAEP')
expect(pkcsLink.closest('a')).toHaveAttribute('href', 'https://pycryptodome.readthedocs.io/en/latest/src/cipher/oaep.html')
// Verify the Jina Reader external link
const jinaLink = screen.getByRole('link', { name: /datasetCreation\.jinaReader\.getApiKeyLinkText/i })
expect(jinaLink).toHaveAttribute('target', '_blank')
})
it('should return early when save is clicked while already saving (isSaving guard)', async () => {
const user = userEvent.setup()
// Arrange - a save that never resolves so isSaving stays true
let resolveFirst: (value: { result: 'success' }) => void
const neverResolves = new Promise<{ result: 'success' }>((resolve) => {
resolveFirst = resolve
})
vi.mocked(createDataSourceApiKeyBinding).mockReturnValue(neverResolves)
render(<ConfigJinaReaderModal onCancel={mockOnCancel} onSaved={mockOnSaved} />)
const apiKeyInput = screen.getByPlaceholderText('datasetCreation.jinaReader.apiKeyPlaceholder')
await user.type(apiKeyInput, 'valid-key')
const saveBtn = screen.getByRole('button', { name: /common\.operation\.save/i })
// First click - starts saving, isSaving becomes true
await user.click(saveBtn)
expect(createDataSourceApiKeyBinding).toHaveBeenCalledTimes(1)
// Second click using fireEvent bypasses disabled check - hits isSaving guard
const { fireEvent: fe } = await import('@testing-library/react')
fe.click(saveBtn)
// Still only called once because isSaving=true returns early
expect(createDataSourceApiKeyBinding).toHaveBeenCalledTimes(1)
// Cleanup
resolveFirst!({ result: 'success' })
await waitFor(() => expect(mockOnSaved).toHaveBeenCalled())
})
})
})