mirror of
https://github.com/langgenius/dify.git
synced 2026-04-25 21:26:15 +08:00
test: enhance unit tests for filter and menu-dropdown components
- Update filter.spec.tsx to improve clarity in test descriptions and add new tests for period and annotation status display. - Refactor menu-dropdown.spec.tsx to utilize screen queries for button interactions, enhancing test reliability and readability. - Ensure all tests cover expected UI behavior for various states and props.
This commit is contained in:
@ -121,7 +121,7 @@ describe('Filter', () => {
|
||||
})
|
||||
|
||||
describe('Query Params', () => {
|
||||
it('should handle different period values', () => {
|
||||
it('should display "today" when period is set to 1', () => {
|
||||
const propsWithPeriod = {
|
||||
...defaultProps,
|
||||
queryParams: { ...defaultQueryParams, period: '1' },
|
||||
@ -129,10 +129,29 @@ describe('Filter', () => {
|
||||
|
||||
render(<Filter {...propsWithPeriod} />)
|
||||
|
||||
expect(screen.getByPlaceholderText('operation.search')).toBeInTheDocument()
|
||||
// Period '1' maps to 'today' in TIME_PERIOD_MAPPING
|
||||
expect(screen.getByText('filter.period.today')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle annotated status', () => {
|
||||
it('should display "last7days" when period is set to 2', () => {
|
||||
const propsWithPeriod = {
|
||||
...defaultProps,
|
||||
queryParams: { ...defaultQueryParams, period: '2' },
|
||||
}
|
||||
|
||||
render(<Filter {...propsWithPeriod} />)
|
||||
|
||||
expect(screen.getByText('filter.period.last7days')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display "allTime" when period is set to 9', () => {
|
||||
render(<Filter {...defaultProps} />)
|
||||
|
||||
// Default period is '9' which maps to 'allTime'
|
||||
expect(screen.getByText('filter.period.allTime')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display annotated status with count when annotation_status is annotated', () => {
|
||||
const propsWithAnnotation = {
|
||||
...defaultProps,
|
||||
queryParams: { ...defaultQueryParams, annotation_status: 'annotated' },
|
||||
@ -140,10 +159,11 @@ describe('Filter', () => {
|
||||
|
||||
render(<Filter {...propsWithAnnotation} />)
|
||||
|
||||
expect(screen.getByPlaceholderText('operation.search')).toBeInTheDocument()
|
||||
// The mock returns count: 10, so the text should include the count
|
||||
expect(screen.getByText('filter.annotation.annotated (10)')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle not_annotated status', () => {
|
||||
it('should display not_annotated status when annotation_status is not_annotated', () => {
|
||||
const propsWithNotAnnotated = {
|
||||
...defaultProps,
|
||||
queryParams: { ...defaultQueryParams, annotation_status: 'not_annotated' },
|
||||
@ -151,7 +171,14 @@ describe('Filter', () => {
|
||||
|
||||
render(<Filter {...propsWithNotAnnotated} />)
|
||||
|
||||
expect(screen.getByPlaceholderText('operation.search')).toBeInTheDocument()
|
||||
expect(screen.getByText('filter.annotation.not_annotated')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display all annotation status when annotation_status is all', () => {
|
||||
render(<Filter {...defaultProps} />)
|
||||
|
||||
// Default annotation_status is 'all'
|
||||
expect(screen.getByText('filter.annotation.all')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -70,18 +70,18 @@ describe('PublishToast', () => {
|
||||
})
|
||||
|
||||
it('should render close button', () => {
|
||||
const { container } = render(<PublishToast />)
|
||||
render(<PublishToast />)
|
||||
|
||||
const closeButton = container.querySelector('.cursor-pointer')
|
||||
const closeButton = screen.getByRole('button', { name: /close/i })
|
||||
expect(closeButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
describe('user interactions', () => {
|
||||
it('should hide toast when close button is clicked', () => {
|
||||
const { container } = render(<PublishToast />)
|
||||
render(<PublishToast />)
|
||||
|
||||
const closeButton = container.querySelector('.cursor-pointer')
|
||||
const closeButton = screen.getByRole('button', { name: /close/i })
|
||||
expect(screen.getByText('publishToast.title')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(closeButton!)
|
||||
@ -90,9 +90,9 @@ describe('PublishToast', () => {
|
||||
})
|
||||
|
||||
it('should remain hidden after close button is clicked', () => {
|
||||
const { container, rerender } = render(<PublishToast />)
|
||||
const { rerender } = render(<PublishToast />)
|
||||
|
||||
const closeButton = container.querySelector('.cursor-pointer')
|
||||
const closeButton = screen.getByRole('button', { name: /close/i })
|
||||
fireEvent.click(closeButton!)
|
||||
|
||||
rerender(<PublishToast />)
|
||||
|
||||
@ -337,11 +337,9 @@ describe('usePipelineInit', () => {
|
||||
|
||||
renderHook(() => usePipelineInit())
|
||||
|
||||
// Give time for potential fetch to occur
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
// fetchWorkflowDraft should still be called due to initial effect
|
||||
// But it might fail due to undefined id
|
||||
await waitFor(() => {
|
||||
expect(mockFetchWorkflowDraft).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { renderHook } from '@testing-library/react'
|
||||
import { renderHook, waitFor } from '@testing-library/react'
|
||||
import { act } from 'react'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
@ -111,35 +111,37 @@ describe('usePipelineRefreshDraft', () => {
|
||||
it('should update workflow canvas with response data', async () => {
|
||||
const { result } = renderHook(() => usePipelineRefreshDraft())
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
result.current.handleRefreshWorkflowDraft()
|
||||
// Wait for promise to resolve
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(mockHandleUpdateWorkflowCanvas).toHaveBeenCalled()
|
||||
await waitFor(() => {
|
||||
expect(mockHandleUpdateWorkflowCanvas).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should update sync hash after fetch', async () => {
|
||||
const { result } = renderHook(() => usePipelineRefreshDraft())
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
result.current.handleRefreshWorkflowDraft()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(mockSetSyncWorkflowDraftHash).toHaveBeenCalledWith('new-hash')
|
||||
await waitFor(() => {
|
||||
expect(mockSetSyncWorkflowDraftHash).toHaveBeenCalledWith('new-hash')
|
||||
})
|
||||
})
|
||||
|
||||
it('should set syncing state to false after completion', async () => {
|
||||
const { result } = renderHook(() => usePipelineRefreshDraft())
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
result.current.handleRefreshWorkflowDraft()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(mockSetIsSyncingWorkflowDraft).toHaveBeenLastCalledWith(false)
|
||||
await waitFor(() => {
|
||||
expect(mockSetIsSyncingWorkflowDraft).toHaveBeenLastCalledWith(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle secret environment variables', async () => {
|
||||
@ -158,12 +160,13 @@ describe('usePipelineRefreshDraft', () => {
|
||||
|
||||
const { result } = renderHook(() => usePipelineRefreshDraft())
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
result.current.handleRefreshWorkflowDraft()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(mockSetEnvSecrets).toHaveBeenCalledWith({ 'env-1': 'secret-value' })
|
||||
await waitFor(() => {
|
||||
expect(mockSetEnvSecrets).toHaveBeenCalledWith({ 'env-1': 'secret-value' })
|
||||
})
|
||||
})
|
||||
|
||||
it('should mask secret values in environment variables', async () => {
|
||||
@ -182,15 +185,16 @@ describe('usePipelineRefreshDraft', () => {
|
||||
|
||||
const { result } = renderHook(() => usePipelineRefreshDraft())
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
result.current.handleRefreshWorkflowDraft()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(mockSetEnvironmentVariables).toHaveBeenCalledWith([
|
||||
{ id: 'env-1', value_type: 'secret', value: '[__HIDDEN__]' },
|
||||
{ id: 'env-2', value_type: 'string', value: 'plain-value' },
|
||||
])
|
||||
await waitFor(() => {
|
||||
expect(mockSetEnvironmentVariables).toHaveBeenCalledWith([
|
||||
{ id: 'env-1', value_type: 'secret', value: '[__HIDDEN__]' },
|
||||
{ id: 'env-2', value_type: 'string', value: 'plain-value' },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle empty environment variables', async () => {
|
||||
@ -206,13 +210,14 @@ describe('usePipelineRefreshDraft', () => {
|
||||
|
||||
const { result } = renderHook(() => usePipelineRefreshDraft())
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
result.current.handleRefreshWorkflowDraft()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(mockSetEnvSecrets).toHaveBeenCalledWith({})
|
||||
expect(mockSetEnvironmentVariables).toHaveBeenCalledWith([])
|
||||
await waitFor(() => {
|
||||
expect(mockSetEnvSecrets).toHaveBeenCalledWith({})
|
||||
expect(mockSetEnvironmentVariables).toHaveBeenCalledWith([])
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle undefined environment variables', async () => {
|
||||
@ -228,13 +233,14 @@ describe('usePipelineRefreshDraft', () => {
|
||||
|
||||
const { result } = renderHook(() => usePipelineRefreshDraft())
|
||||
|
||||
await act(async () => {
|
||||
act(() => {
|
||||
result.current.handleRefreshWorkflowDraft()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(mockSetEnvSecrets).toHaveBeenCalledWith({})
|
||||
expect(mockSetEnvironmentVariables).toHaveBeenCalledWith([])
|
||||
await waitFor(() => {
|
||||
expect(mockSetEnvSecrets).toHaveBeenCalledWith({})
|
||||
expect(mockSetEnvironmentVariables).toHaveBeenCalledWith([])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -57,8 +57,8 @@ describe('MenuDropdown', () => {
|
||||
it('should render the trigger button', () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} />)
|
||||
|
||||
// The trigger button contains an icon
|
||||
const triggerButton = document.querySelector('button')
|
||||
// The trigger button contains a settings icon (RiEqualizer2Line)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
expect(triggerButton).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -72,8 +72,8 @@ describe('MenuDropdown', () => {
|
||||
it('should show dropdown content when clicked', async () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('theme.theme')).toBeInTheDocument()
|
||||
@ -83,8 +83,8 @@ describe('MenuDropdown', () => {
|
||||
it('should show About option in dropdown', async () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('userProfile.about')).toBeInTheDocument()
|
||||
@ -101,8 +101,8 @@ describe('MenuDropdown', () => {
|
||||
|
||||
render(<MenuDropdown data={siteInfoWithPrivacy} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('chat.privacyPolicyMiddle')).toBeInTheDocument()
|
||||
@ -112,8 +112,8 @@ describe('MenuDropdown', () => {
|
||||
it('should not show privacy policy link when not provided', async () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('chat.privacyPolicyMiddle')).not.toBeInTheDocument()
|
||||
@ -129,8 +129,8 @@ describe('MenuDropdown', () => {
|
||||
|
||||
render(<MenuDropdown data={siteInfoWithPrivacy} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
const link = screen.getByText('chat.privacyPolicyMiddle').closest('a')
|
||||
@ -144,8 +144,8 @@ describe('MenuDropdown', () => {
|
||||
it('should show logout option when hideLogout is false', async () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} hideLogout={false} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('userProfile.logout')).toBeInTheDocument()
|
||||
@ -155,8 +155,8 @@ describe('MenuDropdown', () => {
|
||||
it('should hide logout option when hideLogout is true', async () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} hideLogout={true} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('userProfile.logout')).not.toBeInTheDocument()
|
||||
@ -166,8 +166,8 @@ describe('MenuDropdown', () => {
|
||||
it('should call webAppLogout and redirect when logout is clicked', async () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} hideLogout={false} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('userProfile.logout')).toBeInTheDocument()
|
||||
@ -189,8 +189,8 @@ describe('MenuDropdown', () => {
|
||||
it('should show InfoModal when About is clicked', async () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('userProfile.about')).toBeInTheDocument()
|
||||
@ -209,8 +209,8 @@ describe('MenuDropdown', () => {
|
||||
it('should close dropdown when forceClose changes to true', async () => {
|
||||
const { rerender } = render(<MenuDropdown data={baseSiteInfo} forceClose={false} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
fireEvent.click(triggerButton!)
|
||||
const triggerButton = screen.getByRole('button')
|
||||
fireEvent.click(triggerButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('theme.theme')).toBeInTheDocument()
|
||||
@ -228,7 +228,7 @@ describe('MenuDropdown', () => {
|
||||
it('should accept custom placement', () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} placement="top-start" />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
const triggerButton = screen.getByRole('button')
|
||||
expect(triggerButton).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@ -237,16 +237,16 @@ describe('MenuDropdown', () => {
|
||||
it('should close dropdown when clicking trigger again', async () => {
|
||||
render(<MenuDropdown data={baseSiteInfo} />)
|
||||
|
||||
const triggerButton = document.querySelector('button')
|
||||
const triggerButton = screen.getByRole('button')
|
||||
|
||||
// Open
|
||||
fireEvent.click(triggerButton!)
|
||||
fireEvent.click(triggerButton)
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('theme.theme')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Close
|
||||
fireEvent.click(triggerButton!)
|
||||
fireEvent.click(triggerButton)
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('theme.theme')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user