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:
CodingOnStar
2026-01-28 17:41:17 +08:00
parent 35eae1f9f4
commit 08d0e534ba
5 changed files with 102 additions and 71 deletions

View File

@ -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()
})
})

View File

@ -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 />)

View File

@ -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()
})
})
})
})

View File

@ -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([])
})
})
})
})

View File

@ -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()
})