refactor(web): complete migration from PortalToFollowElem to Popover component in tests

This commit finalizes the transition from the PortalToFollowElem to the Popover component across various test files. The updates include the implementation of Popover's context and trigger handling, ensuring consistent behavior in the UI. All relevant tests have been adjusted to reflect these changes, enhancing the overall test coverage and reliability.
This commit is contained in:
CodingOnStar
2026-04-21 15:04:52 +08:00
parent f608bc8752
commit 94271b5d63
5 changed files with 292 additions and 42 deletions

View File

@@ -10,6 +10,72 @@ vi.mock('@/next/navigation', () => ({
usePathname: () => '/test',
}))
vi.mock('@langgenius/dify-ui/popover', async () => {
const React = await import('react')
const PopoverContext = React.createContext({
open: false,
setOpen: (_open: boolean) => {},
})
const Popover = ({
children,
open: controlledOpen,
onOpenChange,
}: {
children: React.ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}) => {
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false)
const isControlled = controlledOpen !== undefined
const open = isControlled ? !!controlledOpen : uncontrolledOpen
const setOpen = (nextOpen: boolean) => {
if (!isControlled)
setUncontrolledOpen(nextOpen)
onOpenChange?.(nextOpen)
}
return (
<PopoverContext.Provider value={{ open, setOpen }}>
{children}
</PopoverContext.Provider>
)
}
const PopoverTrigger = ({ render }: { render: React.ReactElement }) => {
const { open, setOpen } = React.useContext(PopoverContext)
return React.cloneElement(render, {
'data-testid': 'popover-trigger',
'onClick': (e: React.MouseEvent<HTMLElement>) => {
render.props.onClick?.(e)
if (!e.defaultPrevented)
setOpen(!open)
},
})
}
const PopoverContent = ({
children,
...props
}: React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode }) => {
const { open } = React.useContext(PopoverContext)
if (!open)
return null
return (
<div data-testid="popover-content" {...props}>
{children}
</div>
)
}
return {
Popover,
PopoverTrigger,
PopoverContent,
}
})
type PortalToFollowElemProps = {
children: React.ReactNode
open?: boolean
@@ -209,20 +275,17 @@ describe('ContextVar', () => {
// Act
render(<ContextVar {...props} />)
const triggers = screen.getAllByTestId('portal-trigger')
const varPickerTrigger = triggers[triggers.length - 1]
const varPickerTrigger = screen.getByTestId('popover-trigger')
await user.click(varPickerTrigger!)
expect(screen.getByTestId('portal-content'))!.toBeInTheDocument()
expect(screen.getByTestId('popover-content'))!.toBeInTheDocument()
// Select a different option
const options = screen.getAllByText('var2')
expect(options.length).toBeGreaterThan(0)
await user.click(options[0]!)
await user.click(screen.getByText('var2'))
// Assert
expect(onChange).toHaveBeenCalledWith('var2')
expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument()
expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument()
})
it('should toggle dropdown when clicking the trigger button', async () => {
@@ -233,16 +296,15 @@ describe('ContextVar', () => {
// Act
render(<ContextVar {...props} />)
const triggers = screen.getAllByTestId('portal-trigger')
const varPickerTrigger = triggers[triggers.length - 1]
const varPickerTrigger = screen.getByTestId('popover-trigger')
// Open dropdown
await user.click(varPickerTrigger!)
expect(screen.getByTestId('portal-content'))!.toBeInTheDocument()
expect(screen.getByTestId('popover-content'))!.toBeInTheDocument()
// Close dropdown
await user.click(varPickerTrigger!)
expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument()
expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument()
})
})

View File

@@ -4,6 +4,61 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { TriggerCredentialTypeEnum } from '@/app/components/workflow/block-selector/types'
import { SubscriptionSelectorEntry } from '../selector-entry'
vi.mock('@langgenius/dify-ui/popover', async () => {
const React = await import('react')
const PopoverContext = React.createContext({
open: false,
setOpen: (_open: boolean) => {},
})
const Popover = ({
children,
open: controlledOpen,
onOpenChange,
}: {
children: React.ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}) => {
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false)
const isControlled = controlledOpen !== undefined
const open = isControlled ? !!controlledOpen : uncontrolledOpen
const setOpen = (nextOpen: boolean) => {
if (!isControlled)
setUncontrolledOpen(nextOpen)
onOpenChange?.(nextOpen)
}
return (
<PopoverContext.Provider value={{ open, setOpen }}>
{children}
</PopoverContext.Provider>
)
}
const PopoverTrigger = ({ render }: { render: React.ReactElement }) => {
const { open, setOpen } = React.useContext(PopoverContext)
return React.cloneElement(render, {
onClick: (e: React.MouseEvent<HTMLElement>) => {
render.props.onClick?.(e)
if (!e.defaultPrevented)
setOpen(!open)
},
})
}
const PopoverContent = ({ children }: { children: React.ReactNode }) => {
const { open } = React.useContext(PopoverContext)
return open ? <div data-testid="popover-content">{children}</div> : null
}
return {
Popover,
PopoverTrigger,
PopoverContent,
}
})
let mockSubscriptions: TriggerSubscription[] = []
const mockRefetch = vi.fn()
@@ -92,6 +147,6 @@ describe('SubscriptionSelectorEntry', () => {
fireEvent.click(screen.getByRole('button', { name: 'Subscription One' }))
expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ id: 'sub-1', name: 'Subscription One' }), expect.any(Function))
expect(screen.queryByText('Subscription One')).not.toBeInTheDocument()
expect(screen.queryByTestId('popover-content')).not.toBeInTheDocument()
})
})

View File

@@ -4,8 +4,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PluginSource } from '@/app/components/plugins/types'
import ToolPicker from '../tool-picker'
let portalOpen = false
const mockInstalledPluginList = vi.hoisted(() => ({
data: {
plugins: [] as PluginDetail[],
@@ -21,33 +19,53 @@ vi.mock('@/app/components/base/loading', () => ({
default: () => <div data-testid="loading">loading</div>,
}))
vi.mock('@/app/components/base/portal-to-follow-elem', async () => {
const _React = await import('react')
vi.mock('@langgenius/dify-ui/popover', async () => {
const React = await import('react')
const PopoverContext = React.createContext({
open: false,
setOpen: (_open: boolean) => {},
})
const Popover = ({
children,
open,
onOpenChange,
}: {
children: React.ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}) => (
<PopoverContext.Provider value={{ open: !!open, setOpen: (nextOpen: boolean) => onOpenChange?.(nextOpen) }}>
{children}
</PopoverContext.Provider>
)
const PopoverTrigger = ({ render }: { render: React.ReactElement }) => {
const { open, setOpen } = React.useContext(PopoverContext)
return React.cloneElement(render, {
onClick: (e: React.MouseEvent<HTMLElement>) => {
render.props.onClick?.(e)
if (!e.defaultPrevented)
setOpen(!open)
},
})
}
const PopoverContent = ({
children,
className,
}: {
children: React.ReactNode
className?: string
}) => {
const { open } = React.useContext(PopoverContext)
return open ? <div data-testid="popover-content" className={className}>{children}</div> : null
}
return {
PortalToFollowElem: ({
open,
children,
}: {
open: boolean
children: React.ReactNode
}) => {
portalOpen = open
return <div>{children}</div>
},
PortalToFollowElemTrigger: ({
children,
onClick,
}: {
children: React.ReactNode
onClick: () => void
}) => <button data-testid="trigger" onClick={onClick}>{children}</button>,
PortalToFollowElemContent: ({
children,
className,
}: {
children: React.ReactNode
className?: string
}) => portalOpen ? <div data-testid="portal-content" className={className}>{children}</div> : null,
Popover,
PopoverTrigger,
PopoverContent,
}
})
@@ -118,7 +136,6 @@ const createPlugin = (
describe('ToolPicker', () => {
beforeEach(() => {
vi.clearAllMocks()
portalOpen = false
mockInstalledPluginList.data = {
plugins: [],
}
@@ -137,7 +154,7 @@ describe('ToolPicker', () => {
/>,
)
fireEvent.click(screen.getByTestId('trigger'))
fireEvent.click(screen.getByText('trigger'))
expect(onShowChange).toHaveBeenCalledWith(true)
})

View File

@@ -2,6 +2,67 @@ import { act, fireEvent, render, screen } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import LabelSelector from '../selector'
vi.mock('@langgenius/dify-ui/popover', async () => {
const React = await import('react')
const PopoverContext = React.createContext({
open: false,
setOpen: (_open: boolean) => {},
})
const Popover = ({
children,
open: controlledOpen,
onOpenChange,
}: {
children: React.ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}) => {
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false)
const isControlled = controlledOpen !== undefined
const open = isControlled ? !!controlledOpen : uncontrolledOpen
const setOpen = (nextOpen: boolean) => {
if (!isControlled)
setUncontrolledOpen(nextOpen)
onOpenChange?.(nextOpen)
}
return (
<PopoverContext.Provider value={{ open, setOpen }}>
{children}
</PopoverContext.Provider>
)
}
const PopoverTrigger = ({ render }: { render: React.ReactElement }) => {
const { open, setOpen } = React.useContext(PopoverContext)
return React.cloneElement(render, {
onClick: (e: React.MouseEvent<HTMLElement>) => {
render.props.onClick?.(e)
if (!e.defaultPrevented)
setOpen(!open)
},
})
}
const PopoverContent = ({
children,
...props
}: React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode }) => {
const { open } = React.useContext(PopoverContext)
if (!open)
return null
return <div {...props}>{children}</div>
}
return {
Popover,
PopoverTrigger,
PopoverContent,
}
})
// Mock useTags hook with controlled test data
const mockTags = [
{ name: 'agent', label: 'Agent' },

View File

@@ -2,6 +2,61 @@ import type { Member } from '@/models/common'
import { fireEvent, render, screen } from '@testing-library/react'
import MemberSelector from '../member-selector'
vi.mock('@langgenius/dify-ui/popover', async () => {
const React = await import('react')
const PopoverContext = React.createContext({
open: false,
setOpen: (_open: boolean) => {},
})
const Popover = ({
children,
open: controlledOpen,
onOpenChange,
}: {
children: import('react').ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}) => {
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false)
const isControlled = controlledOpen !== undefined
const open = isControlled ? !!controlledOpen : uncontrolledOpen
const setOpen = (nextOpen: boolean) => {
if (!isControlled)
setUncontrolledOpen(nextOpen)
onOpenChange?.(nextOpen)
}
return (
<PopoverContext.Provider value={{ open, setOpen }}>
{children}
</PopoverContext.Provider>
)
}
const PopoverTrigger = ({ render }: { render: import('react').ReactElement }) => {
const { open, setOpen } = React.useContext(PopoverContext)
return React.cloneElement(render, {
onClick: (e: import('react').MouseEvent<HTMLElement>) => {
render.props.onClick?.(e)
if (!e.defaultPrevented)
setOpen(!open)
},
})
}
const PopoverContent = ({ children }: { children: import('react').ReactNode }) => {
const { open } = React.useContext(PopoverContext)
return open ? <div data-testid="popover-content">{children}</div> : null
}
return {
Popover,
PopoverTrigger,
PopoverContent,
}
})
const mockMemberList = vi.hoisted(() => vi.fn())
vi.mock('../member-list', () => ({