mirror of
https://github.com/langgenius/dify.git
synced 2025-12-19 22:28:46 +00:00
test: add comprehensive Jest tests for ConfirmModal component (#29627)
Signed-off-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
import React from 'react'
|
||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import ConfirmModal from './index'
|
||||
|
||||
// Mock external dependencies as per guidelines
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Test utilities
|
||||
const defaultProps = {
|
||||
show: true,
|
||||
onClose: jest.fn(),
|
||||
onConfirm: jest.fn(),
|
||||
}
|
||||
|
||||
const renderComponent = (props: Partial<React.ComponentProps<typeof ConfirmModal>> = {}) => {
|
||||
const mergedProps = { ...defaultProps, ...props }
|
||||
return render(<ConfirmModal {...mergedProps} />)
|
||||
}
|
||||
|
||||
describe('ConfirmModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
// Rendering tests (REQUIRED)
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render when show prop is true', () => {
|
||||
// Arrange & Act
|
||||
renderComponent({ show: true })
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render when show prop is false', () => {
|
||||
// Arrange & Act
|
||||
renderComponent({ show: false })
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render warning icon with proper styling', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
const iconContainer = document.querySelector('.rounded-xl')
|
||||
expect(iconContainer).toBeInTheDocument()
|
||||
expect(iconContainer).toHaveClass('border-[0.5px]')
|
||||
expect(iconContainer).toHaveClass('bg-background-section')
|
||||
})
|
||||
|
||||
it('should render translated title and description', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('tools.createTool.confirmTitle')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.createTool.confirmTip')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render action buttons with translated text', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByText('common.operation.cancel')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.operation.confirm')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Props tests (REQUIRED)
|
||||
describe('Props', () => {
|
||||
it('should handle missing onConfirm prop gracefully', () => {
|
||||
// Arrange & Act - Should not crash when onConfirm is undefined
|
||||
expect(() => {
|
||||
renderComponent({ onConfirm: undefined })
|
||||
}).not.toThrow()
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
||||
expect(screen.getByText('common.operation.confirm')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply default styling and width constraints', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert - Check for the dialog panel with modal content
|
||||
// The real modal structure has nested divs, we need to find the one with our classes
|
||||
const dialogContent = document.querySelector('.relative.rounded-2xl')
|
||||
expect(dialogContent).toBeInTheDocument()
|
||||
expect(dialogContent).toHaveClass('w-[600px]')
|
||||
expect(dialogContent).toHaveClass('max-w-[600px]')
|
||||
expect(dialogContent).toHaveClass('p-8')
|
||||
})
|
||||
})
|
||||
|
||||
// User Interactions
|
||||
describe('User Interactions', () => {
|
||||
it('should call onClose when close button is clicked', async () => {
|
||||
// Arrange
|
||||
const user = userEvent.setup()
|
||||
const onClose = jest.fn()
|
||||
renderComponent({ onClose })
|
||||
|
||||
// Act - Find the close button and click it
|
||||
const closeButton = document.querySelector('.cursor-pointer')
|
||||
expect(closeButton).toBeInTheDocument() // Ensure the button is found before clicking
|
||||
await user.click(closeButton!)
|
||||
|
||||
// Assert
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call onClose when cancel button is clicked', async () => {
|
||||
// Arrange
|
||||
const user = userEvent.setup()
|
||||
const onClose = jest.fn()
|
||||
renderComponent({ onClose })
|
||||
|
||||
// Act
|
||||
const cancelButton = screen.getByText('common.operation.cancel')
|
||||
await user.click(cancelButton)
|
||||
|
||||
// Assert
|
||||
expect(onClose).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call onConfirm when confirm button is clicked', async () => {
|
||||
// Arrange
|
||||
const user = userEvent.setup()
|
||||
const onConfirm = jest.fn()
|
||||
renderComponent({ onConfirm })
|
||||
|
||||
// Act
|
||||
const confirmButton = screen.getByText('common.operation.confirm')
|
||||
await user.click(confirmButton)
|
||||
|
||||
// Assert
|
||||
expect(onConfirm).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not throw error when confirm button is clicked without onConfirm', async () => {
|
||||
// Arrange
|
||||
const user = userEvent.setup()
|
||||
renderComponent({ onConfirm: undefined })
|
||||
const confirmButton = screen.getByText('common.operation.confirm')
|
||||
|
||||
// Act & Assert - This will fail the test if user.click throws an unhandled error
|
||||
await user.click(confirmButton)
|
||||
})
|
||||
|
||||
it('should have correct button variants', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
const confirmButton = screen.getByText('common.operation.confirm')
|
||||
expect(confirmButton).toHaveClass('btn-warning')
|
||||
})
|
||||
})
|
||||
|
||||
// Edge Cases (REQUIRED)
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle rapid show/hide toggling', async () => {
|
||||
// Arrange
|
||||
const { rerender } = renderComponent({ show: false })
|
||||
|
||||
// Assert - Initially not shown
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||
|
||||
// Act - Show modal
|
||||
await act(async () => {
|
||||
rerender(<ConfirmModal {...defaultProps} show={true} />)
|
||||
})
|
||||
|
||||
// Assert - Now shown
|
||||
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
||||
|
||||
// Act - Hide modal again
|
||||
await act(async () => {
|
||||
rerender(<ConfirmModal {...defaultProps} show={false} />)
|
||||
})
|
||||
|
||||
// Assert - Hidden again (wait for transition to complete)
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle multiple quick clicks on close button', async () => {
|
||||
// Arrange
|
||||
const user = userEvent.setup()
|
||||
const onClose = jest.fn()
|
||||
renderComponent({ onClose })
|
||||
|
||||
const closeButton = document.querySelector('.cursor-pointer')
|
||||
expect(closeButton).toBeInTheDocument() // Ensure the button is found before clicking
|
||||
|
||||
// Act
|
||||
await user.click(closeButton!)
|
||||
await user.click(closeButton!)
|
||||
await user.click(closeButton!)
|
||||
|
||||
// Assert
|
||||
expect(onClose).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('should handle multiple quick clicks on confirm button', async () => {
|
||||
// Arrange
|
||||
const user = userEvent.setup()
|
||||
const onConfirm = jest.fn()
|
||||
renderComponent({ onConfirm })
|
||||
|
||||
// Act
|
||||
const confirmButton = screen.getByText('common.operation.confirm')
|
||||
await user.click(confirmButton)
|
||||
await user.click(confirmButton)
|
||||
await user.click(confirmButton)
|
||||
|
||||
// Assert
|
||||
expect(onConfirm).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('should handle multiple quick clicks on cancel button', async () => {
|
||||
// Arrange
|
||||
const user = userEvent.setup()
|
||||
const onClose = jest.fn()
|
||||
renderComponent({ onClose })
|
||||
|
||||
// Act - Click cancel button twice
|
||||
const cancelButton = screen.getByText('common.operation.cancel')
|
||||
await user.click(cancelButton)
|
||||
await user.click(cancelButton)
|
||||
|
||||
// Assert
|
||||
expect(onClose).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
|
||||
// Accessibility tests
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper button roles', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
const buttons = screen.getAllByRole('button')
|
||||
expect(buttons).toHaveLength(2)
|
||||
expect(buttons[0]).toHaveTextContent('common.operation.cancel')
|
||||
expect(buttons[1]).toHaveTextContent('common.operation.confirm')
|
||||
})
|
||||
|
||||
it('should have proper text hierarchy', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
const title = screen.getByText('tools.createTool.confirmTitle')
|
||||
expect(title).toBeInTheDocument()
|
||||
|
||||
const description = screen.getByText('tools.createTool.confirmTip')
|
||||
expect(description).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have focusable interactive elements', () => {
|
||||
// Arrange & Act
|
||||
renderComponent()
|
||||
|
||||
// Assert
|
||||
const buttons = screen.getAllByRole('button')
|
||||
buttons.forEach((button) => {
|
||||
expect(button).toBeEnabled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user