import type { ReactElement } from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { renderWithSystemFeatures } from '@/__tests__/utils/mock-system-features' import InstallPluginDropdown from '../install-plugin-dropdown' let portalOpen = false const { mockSystemFeatures, } = vi.hoisted(() => ({ mockSystemFeatures: { enable_marketplace: true, plugin_installation_permission: { restrict_to_marketplace_only: false, }, }, })) vi.mock('@/config', async (importOriginal) => { const actual = await importOriginal() return { ...actual, SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS: '.difypkg,.zip', } }) const render = (ui: ReactElement) => renderWithSystemFeatures(ui, { systemFeatures: mockSystemFeatures }) vi.mock('@/app/components/base/icons/src/vender/solid/files', () => ({ FileZip: () => file, })) vi.mock('@/app/components/base/icons/src/vender/solid/general', () => ({ Github: () => github, })) vi.mock('@/app/components/base/icons/src/vender/solid/mediaAndDevices', () => ({ MagicBox: () => magic, })) vi.mock('@langgenius/dify-ui/button', () => ({ Button: ({ children, onClick, className, ...props }: React.ButtonHTMLAttributes) => ( ), })) vi.mock('@langgenius/dify-ui/dropdown-menu', async () => { const React = await import('react') const DropdownMenuContext = React.createContext<{ isOpen: boolean, setOpen: (open: boolean) => void } | null>(null) const useDropdownMenuContext = () => { const context = React.use(DropdownMenuContext) if (!context) throw new Error('DropdownMenu components must be wrapped in DropdownMenu') return context } return { DropdownMenu: ({ open, onOpenChange, children, }: { open: boolean onOpenChange?: (open: boolean) => void children: React.ReactNode }) => { portalOpen = open return (
{children}
) }, DropdownMenuTrigger: ({ children, onClick, render, }: { children: React.ReactNode onClick?: React.MouseEventHandler render?: React.ReactElement }) => { const { isOpen, setOpen } = useDropdownMenuContext() const handleClick = (e: React.MouseEvent) => { onClick?.(e) setOpen(!isOpen) } if (render) return React.cloneElement(render, { 'data-testid': 'dropdown-trigger', 'onClick': handleClick } as Record, children) return }, DropdownMenuContent: ({ children, }: { children: React.ReactNode }) => portalOpen ?
{children}
: null, DropdownMenuItem: ({ children, onClick, }: { children: React.ReactNode onClick?: React.MouseEventHandler }) => { const { setOpen } = useDropdownMenuContext() return ( ) }, } }) vi.mock('@/app/components/plugins/install-plugin/install-from-github', () => ({ default: ({ onClose }: { onClose: () => void }) => (
), })) vi.mock('@/app/components/plugins/install-plugin/install-from-local-package', () => ({ default: ({ file, onClose, }: { file: File onClose: () => void }) => (
{file.name}
), })) describe('InstallPluginDropdown', () => { beforeEach(() => { vi.clearAllMocks() portalOpen = false mockSystemFeatures.enable_marketplace = true mockSystemFeatures.plugin_installation_permission.restrict_to_marketplace_only = false }) it('shows all install methods when marketplace and custom installs are enabled', () => { render() fireEvent.click(screen.getByTestId('dropdown-trigger')) expect(screen.getByText('plugin.installFrom')).toBeInTheDocument() expect(screen.getByText('plugin.source.marketplace')).toBeInTheDocument() expect(screen.getByText('plugin.source.github')).toBeInTheDocument() expect(screen.getByText('plugin.source.local')).toBeInTheDocument() }) it('shows only marketplace when installation is restricted', () => { mockSystemFeatures.plugin_installation_permission.restrict_to_marketplace_only = true render() fireEvent.click(screen.getByTestId('dropdown-trigger')) expect(screen.getByText('plugin.source.marketplace')).toBeInTheDocument() expect(screen.queryByText('plugin.source.github')).not.toBeInTheDocument() expect(screen.queryByText('plugin.source.local')).not.toBeInTheDocument() }) it('switches to marketplace when the marketplace action is selected', () => { const onSwitchToMarketplaceTab = vi.fn() render() fireEvent.click(screen.getByTestId('dropdown-trigger')) fireEvent.click(screen.getByText('plugin.source.marketplace')) expect(onSwitchToMarketplaceTab).toHaveBeenCalledTimes(1) }) it('opens the github installer when github is selected', async () => { render() fireEvent.click(screen.getByTestId('dropdown-trigger')) fireEvent.click(screen.getByText('plugin.source.github')) expect(await screen.findByTestId('github-modal')).toBeInTheDocument() }) it('opens the local package installer when a file is selected', () => { const { container } = render() fireEvent.click(screen.getByTestId('dropdown-trigger')) fireEvent.change(container.querySelector('input[type="file"]')!, { target: { files: [new File(['content'], 'plugin.difypkg')], }, }) expect(screen.getByTestId('local-modal')).toBeInTheDocument() expect(screen.getByText('plugin.difypkg')).toBeInTheDocument() }) it('triggers the hidden file input when local is selected from the menu', () => { const clickSpy = vi.spyOn(HTMLInputElement.prototype, 'click') render() fireEvent.click(screen.getByTestId('dropdown-trigger')) fireEvent.click(screen.getByText('plugin.source.local')) expect(clickSpy).toHaveBeenCalledTimes(1) clickSpy.mockRestore() }) it('closes the github installer when the modal requests close', async () => { render() fireEvent.click(screen.getByTestId('dropdown-trigger')) fireEvent.click(screen.getByText('plugin.source.github')) fireEvent.click(await screen.findByTestId('close-github-modal')) expect(screen.queryByTestId('github-modal')).not.toBeInTheDocument() }) it('closes the local package installer when the modal requests close', () => { const { container } = render() fireEvent.click(screen.getByTestId('dropdown-trigger')) fireEvent.change(container.querySelector('input[type="file"]')!, { target: { files: [new File(['content'], 'plugin.difypkg')], }, }) fireEvent.click(screen.getByTestId('close-local-modal')) expect(screen.queryByTestId('local-modal')).not.toBeInTheDocument() }) })