mirror of
https://github.com/langgenius/dify.git
synced 2026-07-27 15:08:35 +08:00
Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
183 lines
6.3 KiB
TypeScript
183 lines
6.3 KiB
TypeScript
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
import {
|
|
getStepByStepTourDropdownMenuContentProps,
|
|
useStepByStepTourControlledDropdown,
|
|
} from '../dropdown-menu'
|
|
|
|
const STEP_BY_STEP_TOUR_HIGHLIGHT_PART_DATA_ATTR = 'data-step-by-step-tour-highlight-part'
|
|
|
|
const getHighlightPartValue = (props: unknown): string | undefined => {
|
|
if (!props || typeof props !== 'object') return undefined
|
|
|
|
const value = (props as Record<string, unknown>)[STEP_BY_STEP_TOUR_HIGHLIGHT_PART_DATA_ATTR]
|
|
return typeof value === 'string' ? value : undefined
|
|
}
|
|
|
|
const noopKeyboardHandler = () => {}
|
|
|
|
function TestDropdown({
|
|
allowTriggerCloseWhileControlled,
|
|
controlledOpen,
|
|
}: {
|
|
allowTriggerCloseWhileControlled?: boolean
|
|
controlledOpen?: boolean
|
|
}) {
|
|
const menu = useStepByStepTourControlledDropdown({
|
|
allowTriggerCloseWhileControlled,
|
|
controlledOpen,
|
|
})
|
|
|
|
return (
|
|
<>
|
|
<button type="button" onClick={() => menu.onOpenChange(!menu.open)}>
|
|
Toggle menu
|
|
</button>
|
|
<button type="button" onClick={menu.close}>
|
|
Close from action
|
|
</button>
|
|
<div
|
|
aria-label="dropdown"
|
|
data-controlled={String(menu.controlled)}
|
|
data-open={String(menu.open)}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
describe('useStepByStepTourControlledDropdown', () => {
|
|
it('keeps ordinary dropdown toggle behavior when no tour controls it', () => {
|
|
render(<TestDropdown />)
|
|
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'false')
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Toggle menu' }))
|
|
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'true')
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Toggle menu' }))
|
|
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'false')
|
|
})
|
|
|
|
it('opens for a tour step without locking the dropdown open', () => {
|
|
render(<TestDropdown controlledOpen />)
|
|
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'true')
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-controlled', 'true')
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Toggle menu' }))
|
|
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'false')
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-controlled', 'false')
|
|
})
|
|
|
|
it('can keep a tour-opened dropdown locked until the tour leaves the step', () => {
|
|
render(<TestDropdown controlledOpen allowTriggerCloseWhileControlled={false} />)
|
|
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'true')
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Toggle menu' }))
|
|
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'true')
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-controlled', 'true')
|
|
})
|
|
|
|
it('closes when the tour leaves the dropdown step', async () => {
|
|
const { rerender } = render(<TestDropdown controlledOpen />)
|
|
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'true')
|
|
|
|
rerender(<TestDropdown />)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText('dropdown')).toHaveAttribute('data-open', 'false')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('getStepByStepTourDropdownMenuContentProps', () => {
|
|
it('blocks presentation menu interactions without letting clicks bubble through', () => {
|
|
const onAction = vi.fn()
|
|
const onBackgroundClick = vi.fn()
|
|
const { popupClassName, popupProps, positionerProps } =
|
|
getStepByStepTourDropdownMenuContentProps({
|
|
highlightPart: 'tour-menu',
|
|
interactionMode: 'presentation',
|
|
})
|
|
|
|
render(
|
|
<div role="button" tabIndex={0} onClick={onBackgroundClick} onKeyDown={noopKeyboardHandler}>
|
|
<div
|
|
data-testid="positioner"
|
|
data-step-by-step-tour-highlight-part={getHighlightPartValue(positionerProps)}
|
|
onClickCapture={positionerProps?.onClickCapture}
|
|
onKeyDownCapture={positionerProps?.onKeyDownCapture}
|
|
onMouseDownCapture={positionerProps?.onMouseDownCapture}
|
|
onPointerDownCapture={positionerProps?.onPointerDownCapture}
|
|
>
|
|
<div
|
|
role="menu"
|
|
aria-hidden={popupProps?.['aria-hidden']}
|
|
onClickCapture={popupProps?.onClickCapture}
|
|
onKeyDownCapture={popupProps?.onKeyDownCapture}
|
|
onMouseDownCapture={popupProps?.onMouseDownCapture}
|
|
onPointerDownCapture={popupProps?.onPointerDownCapture}
|
|
>
|
|
<button type="button" role="menuitem" onClick={onAction}>
|
|
Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByRole('menuitem', { name: 'Delete', hidden: true }))
|
|
|
|
expect(onAction).not.toHaveBeenCalled()
|
|
expect(onBackgroundClick).not.toHaveBeenCalled()
|
|
expect(screen.getByRole('menu', { hidden: true })).toHaveAttribute('aria-hidden', 'true')
|
|
expect(screen.getByTestId('positioner')).toHaveAttribute(
|
|
'data-step-by-step-tour-highlight-part',
|
|
'tour-menu',
|
|
)
|
|
expect(popupClassName).toContain('pointer-events-none')
|
|
})
|
|
|
|
it('leaves interactive menus clickable without bubbling through', () => {
|
|
const onAction = vi.fn()
|
|
const onBackgroundClick = vi.fn()
|
|
const onPopupClick = vi.fn()
|
|
const { popupProps, positionerProps } = getStepByStepTourDropdownMenuContentProps({
|
|
highlightPart: 'tour-menu',
|
|
interactionMode: 'interactive',
|
|
popupProps: {
|
|
onClick: onPopupClick,
|
|
},
|
|
})
|
|
|
|
render(
|
|
<div role="button" tabIndex={0} onClick={onBackgroundClick} onKeyDown={noopKeyboardHandler}>
|
|
<div data-step-by-step-tour-highlight-part={getHighlightPartValue(positionerProps)}>
|
|
<div
|
|
role="menu"
|
|
tabIndex={-1}
|
|
aria-hidden={popupProps?.['aria-hidden']}
|
|
onClick={popupProps?.onClick}
|
|
onKeyDown={noopKeyboardHandler}
|
|
>
|
|
<button type="button" role="menuitem" onClick={onAction}>
|
|
Create
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
)
|
|
|
|
fireEvent.click(screen.getByRole('menuitem', { name: 'Create' }))
|
|
|
|
expect(onAction).toHaveBeenCalledTimes(1)
|
|
expect(onPopupClick).toHaveBeenCalledTimes(1)
|
|
expect(onBackgroundClick).not.toHaveBeenCalled()
|
|
})
|
|
})
|