mirror of
https://github.com/langgenius/dify.git
synced 2026-02-24 18:05:11 +00:00
191 lines
6.8 KiB
TypeScript
191 lines
6.8 KiB
TypeScript
import type { TFunction } from 'i18next'
|
|
import type { ModalContextState } from '@/context/modal-context'
|
|
import type { ApiBasedExtension } from '@/models/common'
|
|
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
|
import * as reactI18next from 'react-i18next'
|
|
import { useModalContext } from '@/context/modal-context'
|
|
import { deleteApiBasedExtension } from '@/service/common'
|
|
import Item from './item'
|
|
|
|
// Mock dependencies
|
|
vi.mock('@/context/modal-context', () => ({
|
|
useModalContext: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/service/common', () => ({
|
|
deleteApiBasedExtension: vi.fn(),
|
|
}))
|
|
|
|
describe('Item Component', () => {
|
|
const mockData: ApiBasedExtension = {
|
|
id: '1',
|
|
name: 'Test Extension',
|
|
api_endpoint: 'https://api.example.com',
|
|
api_key: 'test-api-key',
|
|
}
|
|
const mockOnUpdate = vi.fn()
|
|
const mockSetShowApiBasedExtensionModal = vi.fn()
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
vi.mocked(useModalContext).mockReturnValue({
|
|
setShowApiBasedExtensionModal: mockSetShowApiBasedExtensionModal,
|
|
} as unknown as ModalContextState)
|
|
})
|
|
|
|
describe('Rendering', () => {
|
|
it('should render extension data correctly', () => {
|
|
// Act
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('Test Extension')).toBeInTheDocument()
|
|
expect(screen.getByText('https://api.example.com')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render with minimal extension data', () => {
|
|
// Arrange
|
|
const minimalData: ApiBasedExtension = { id: '2' }
|
|
|
|
// Act
|
|
render(<Item data={minimalData} onUpdate={mockOnUpdate} />)
|
|
|
|
// Assert
|
|
expect(screen.getByText('common.operation.edit')).toBeInTheDocument()
|
|
expect(screen.getByText('common.operation.delete')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Modal Interactions', () => {
|
|
it('should open edit modal with correct payload when clicking edit button', () => {
|
|
// Act
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
fireEvent.click(screen.getByText('common.operation.edit'))
|
|
|
|
// Assert
|
|
expect(mockSetShowApiBasedExtensionModal).toHaveBeenCalledWith(expect.objectContaining({
|
|
payload: mockData,
|
|
}))
|
|
const lastCall = mockSetShowApiBasedExtensionModal.mock.calls[0][0]
|
|
if (typeof lastCall === 'object' && lastCall !== null && 'onSaveCallback' in lastCall)
|
|
expect(lastCall.onSaveCallback).toBeInstanceOf(Function)
|
|
})
|
|
|
|
it('should execute onUpdate callback when edit modal save callback is invoked', () => {
|
|
// Act
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
fireEvent.click(screen.getByText('common.operation.edit'))
|
|
|
|
// Assert
|
|
const modalCallArg = mockSetShowApiBasedExtensionModal.mock.calls[0][0]
|
|
if (typeof modalCallArg === 'object' && modalCallArg !== null && 'onSaveCallback' in modalCallArg) {
|
|
const onSaveCallback = modalCallArg.onSaveCallback
|
|
if (onSaveCallback) {
|
|
onSaveCallback()
|
|
expect(mockOnUpdate).toHaveBeenCalledTimes(1)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('Deletion', () => {
|
|
it('should show delete confirmation dialog when clicking delete button', () => {
|
|
// Act
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
fireEvent.click(screen.getByText('common.operation.delete'))
|
|
|
|
// Assert
|
|
expect(screen.getByText(/common\.operation\.delete.*Test Extension.*\?/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should call delete API and triggers onUpdate when confirming deletion', async () => {
|
|
// Arrange
|
|
vi.mocked(deleteApiBasedExtension).mockResolvedValue({ result: 'success' })
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('common.operation.delete'))
|
|
const dialog = screen.getByTestId('confirm-overlay')
|
|
const confirmButton = within(dialog).getByText('common.operation.delete')
|
|
fireEvent.click(confirmButton)
|
|
|
|
// Assert
|
|
await waitFor(() => {
|
|
expect(deleteApiBasedExtension).toHaveBeenCalledWith('/api-based-extension/1')
|
|
expect(mockOnUpdate).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
it('should hide delete confirmation dialog after successful deletion', async () => {
|
|
// Arrange
|
|
vi.mocked(deleteApiBasedExtension).mockResolvedValue({ result: 'success' })
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
|
|
// Act
|
|
fireEvent.click(screen.getByText('common.operation.delete'))
|
|
const dialog = screen.getByTestId('confirm-overlay')
|
|
const confirmButton = within(dialog).getByText('common.operation.delete')
|
|
fireEvent.click(confirmButton)
|
|
|
|
// Assert
|
|
await waitFor(() => {
|
|
expect(screen.queryByText(/common\.operation\.delete.*Test Extension.*\?/i)).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('should close delete confirmation when clicking cancel button', () => {
|
|
// Act
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
fireEvent.click(screen.getByText('common.operation.delete'))
|
|
fireEvent.click(screen.getByText('common.operation.cancel'))
|
|
|
|
// Assert
|
|
expect(screen.queryByText(/common\.operation\.delete.*Test Extension.*\?/i)).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('should not call delete API when canceling deletion', () => {
|
|
// Act
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
fireEvent.click(screen.getByText('common.operation.delete'))
|
|
fireEvent.click(screen.getByText('common.operation.cancel'))
|
|
|
|
// Assert
|
|
expect(deleteApiBasedExtension).not.toHaveBeenCalled()
|
|
expect(mockOnUpdate).not.toHaveBeenCalled()
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should still show confirmation modal when operation.delete translation is missing', () => {
|
|
// Arrange
|
|
const useTranslationSpy = vi.spyOn(reactI18next, 'useTranslation')
|
|
const originalValue = useTranslationSpy.getMockImplementation()?.() || {
|
|
t: (key: string) => key,
|
|
i18n: { language: 'en', changeLanguage: vi.fn() },
|
|
}
|
|
|
|
useTranslationSpy.mockReturnValue({
|
|
...originalValue,
|
|
t: vi.fn().mockImplementation((key: string) => {
|
|
if (key === 'operation.delete')
|
|
return ''
|
|
return key
|
|
}) as unknown as TFunction,
|
|
} as unknown as ReturnType<typeof reactI18next.useTranslation>)
|
|
|
|
// Act
|
|
render(<Item data={mockData} onUpdate={mockOnUpdate} />)
|
|
const allButtons = screen.getAllByRole('button')
|
|
const editBtn = screen.getByText('operation.edit')
|
|
const deleteBtn = allButtons.find(btn => btn !== editBtn)
|
|
if (deleteBtn)
|
|
fireEvent.click(deleteBtn)
|
|
|
|
// Assert
|
|
expect(screen.getByText(/.*Test Extension.*\?/i)).toBeInTheDocument()
|
|
|
|
useTranslationSpy.mockRestore()
|
|
})
|
|
})
|
|
})
|