import type { Dependency, GitHubItemAndMarketPlaceDependency, InstallStatus, PackageDependency, Plugin, PluginDeclaration, VersionProps } from '../../types' import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { InstallStep, PluginCategoryEnum } from '../../types' import InstallBundle, { InstallType } from './index' import GithubItem from './item/github-item' import LoadedItem from './item/loaded-item' import MarketplaceItem from './item/marketplace-item' import PackageItem from './item/package-item' import ReadyToInstall from './ready-to-install' import Installed from './steps/installed' // Factory functions for test data const createMockPlugin = (overrides: Partial = {}): Plugin => ({ type: 'plugin', org: 'test-org', name: 'Test Plugin', plugin_id: 'test-plugin-id', version: '1.0.0', latest_version: '1.0.0', latest_package_identifier: 'test-package-id', icon: 'test-icon.png', verified: true, label: { 'en-US': 'Test Plugin' }, brief: { 'en-US': 'A test plugin' }, description: { 'en-US': 'A test plugin description' }, introduction: 'Introduction text', repository: 'https://github.com/test/plugin', category: PluginCategoryEnum.tool, install_count: 100, endpoint: { settings: [] }, tags: [], badges: [], verification: { authorized_category: 'community' }, from: 'marketplace', ...overrides, }) const createMockVersionProps = (overrides: Partial = {}): VersionProps => ({ hasInstalled: false, installedVersion: undefined, toInstallVersion: '1.0.0', ...overrides, }) const createMockInstallStatus = (overrides: Partial = {}): InstallStatus => ({ success: true, isFromMarketPlace: true, ...overrides, }) const createMockGitHubDependency = (): GitHubItemAndMarketPlaceDependency => ({ type: 'github', value: { repo: 'test-org/test-repo', version: 'v1.0.0', package: 'plugin.zip', }, }) const createMockPackageDependency = (): PackageDependency => ({ type: 'package', value: { unique_identifier: 'package-plugin-uid', manifest: { plugin_unique_identifier: 'package-plugin-uid', version: '1.0.0', author: 'test-author', icon: 'icon.png', name: 'Package Plugin', category: PluginCategoryEnum.tool, label: { 'en-US': 'Package Plugin' } as Record, description: { 'en-US': 'Test package plugin' } as Record, created_at: '2024-01-01', resource: {}, plugins: [], verified: true, endpoint: { settings: [], endpoints: [] }, model: null, tags: [], agent_strategy: null, meta: { version: '1.0.0' }, trigger: {} as PluginDeclaration['trigger'], }, }, }) const createMockDependency = (overrides: Partial = {}): Dependency => ({ type: 'marketplace', value: { plugin_unique_identifier: 'test-plugin-uid', }, ...overrides, } as Dependency) const createMockDependencies = (): Dependency[] => [ { type: 'marketplace', value: { marketplace_plugin_unique_identifier: 'plugin-1-uid', }, }, { type: 'github', value: { repo: 'test/plugin2', version: 'v1.0.0', package: 'plugin2.zip', }, }, { type: 'package', value: { unique_identifier: 'package-plugin-uid', manifest: { plugin_unique_identifier: 'package-plugin-uid', version: '1.0.0', author: 'test-author', icon: 'icon.png', name: 'Package Plugin', category: PluginCategoryEnum.tool, label: { 'en-US': 'Package Plugin' } as Record, description: { 'en-US': 'Test package plugin' } as Record, created_at: '2024-01-01', resource: {}, plugins: [], verified: true, endpoint: { settings: [], endpoints: [] }, model: null, tags: [], agent_strategy: null, meta: { version: '1.0.0' }, trigger: {} as PluginDeclaration['trigger'], }, }, }, ] // Mock useHideLogic hook let mockHideLogicState = { modalClassName: 'test-modal-class', foldAnimInto: vi.fn(), setIsInstalling: vi.fn(), handleStartToInstall: vi.fn(), } vi.mock('../hooks/use-hide-logic', () => ({ default: () => mockHideLogicState, })) // Mock useGetIcon hook vi.mock('../base/use-get-icon', () => ({ default: () => ({ getIconUrl: (icon: string) => icon || 'default-icon.png', }), })) // Mock usePluginInstallLimit hook vi.mock('../hooks/use-install-plugin-limit', () => ({ default: () => ({ canInstall: true }), pluginInstallLimit: () => ({ canInstall: true }), })) // Mock useUploadGitHub hook const mockUseUploadGitHub = vi.fn() vi.mock('@/service/use-plugins', () => ({ useUploadGitHub: (params: { repo: string, version: string, package: string }) => mockUseUploadGitHub(params), useInstallOrUpdate: () => ({ mutate: vi.fn(), isPending: false }), usePluginTaskList: () => ({ handleRefetch: vi.fn() }), useFetchPluginsInMarketPlaceByInfo: () => ({ isLoading: false, data: null, error: null }), })) // Mock config vi.mock('@/config', () => ({ MARKETPLACE_API_PREFIX: 'https://marketplace.example.com', })) // Mock mitt context vi.mock('@/context/mitt-context', () => ({ useMittContextSelector: () => vi.fn(), })) // Mock global public context vi.mock('@/context/global-public-context', () => ({ useGlobalPublicStore: () => ({}), })) // Mock useCanInstallPluginFromMarketplace vi.mock('@/app/components/plugins/plugin-page/use-reference-setting', () => ({ useCanInstallPluginFromMarketplace: () => ({ canInstallPluginFromMarketplace: true }), })) // Mock checkTaskStatus vi.mock('../base/check-task-status', () => ({ default: () => ({ check: vi.fn(), stop: vi.fn() }), })) // Mock useRefreshPluginList vi.mock('../hooks/use-refresh-plugin-list', () => ({ default: () => ({ refreshPluginList: vi.fn() }), })) // Mock useCheckInstalled vi.mock('../hooks/use-check-installed', () => ({ default: () => ({ installedInfo: {} }), })) // Mock ReadyToInstall child component to test InstallBundle in isolation vi.mock('./ready-to-install', () => ({ default: ({ step, onStepChange, onStartToInstall, setIsInstalling, allPlugins, onClose, }: { step: InstallStep onStepChange: (step: InstallStep) => void onStartToInstall: () => void setIsInstalling: (isInstalling: boolean) => void allPlugins: Dependency[] onClose: () => void }) => (
{step} {allPlugins?.length || 0}
), })) describe('InstallBundle', () => { const defaultProps = { fromDSLPayload: createMockDependencies(), onClose: vi.fn(), } beforeEach(() => { vi.clearAllMocks() mockHideLogicState = { modalClassName: 'test-modal-class', foldAnimInto: vi.fn(), setIsInstalling: vi.fn(), handleStartToInstall: vi.fn(), } }) // ================================ // Rendering Tests // ================================ describe('Rendering', () => { it('should render modal with correct title for install plugin', () => { render() expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should render ReadyToInstall component', () => { render() expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() }) it('should integrate with useHideLogic hook', () => { render() // Verify that the component integrates with useHideLogic // The hook provides modalClassName, foldAnimInto, setIsInstalling, handleStartToInstall expect(mockHideLogicState.modalClassName).toBeDefined() expect(mockHideLogicState.foldAnimInto).toBeDefined() }) it('should render modal as visible', () => { render() // Modal is always shown (isShow={true}) expect(screen.getByText('plugin.installModal.installPlugin')).toBeVisible() }) }) // ================================ // Props Tests // ================================ describe('Props', () => { describe('installType', () => { it('should default to InstallType.fromMarketplace when not provided', () => { render() // When installType is fromMarketplace (default), initial step should be readyToInstall expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.readyToInstall) }) it('should set initial step to readyToInstall when installType is fromMarketplace', () => { render() expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.readyToInstall) }) it('should set initial step to uploading when installType is fromLocal', () => { render() expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.uploading) }) it('should set initial step to uploading when installType is fromDSL', () => { render() expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.uploading) }) }) describe('fromDSLPayload', () => { it('should pass allPlugins to ReadyToInstall', () => { const plugins = createMockDependencies() render() expect(screen.getByTestId('plugins-count')).toHaveTextContent('3') }) it('should handle empty fromDSLPayload array', () => { render() expect(screen.getByTestId('plugins-count')).toHaveTextContent('0') }) it('should handle single plugin in fromDSLPayload', () => { render() expect(screen.getByTestId('plugins-count')).toHaveTextContent('1') }) }) describe('onClose', () => { it('should pass onClose to ReadyToInstall', () => { const onClose = vi.fn() render() fireEvent.click(screen.getByTestId('close-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) }) }) // ================================ // State Management Tests // ================================ describe('State Management', () => { it('should update title when step changes to uploadFailed', () => { render() // Initial title expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() // Change step to uploadFailed fireEvent.click(screen.getByTestId('change-to-upload-failed')) expect(screen.getByText('plugin.installModal.uploadFailed')).toBeInTheDocument() }) it('should update title when step changes to installed', () => { render() // Change step to installed fireEvent.click(screen.getByTestId('change-to-installed')) expect(screen.getByText('plugin.installModal.installComplete')).toBeInTheDocument() }) it('should maintain installPlugin title for readyToInstall step', () => { render() expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() // Explicitly change to readyToInstall fireEvent.click(screen.getByTestId('change-to-ready')) expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should pass step state to ReadyToInstall component', () => { render() expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.readyToInstall) }) it('should update ReadyToInstall step when onStepChange is called', () => { render() // Initially readyToInstall expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.readyToInstall) // Change to installed fireEvent.click(screen.getByTestId('change-to-installed')) expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.installed) }) }) // ================================ // Callback Stability and useHideLogic Integration Tests // ================================ describe('Callback Stability and useHideLogic Integration', () => { it('should provide foldAnimInto for modal onClose handler', () => { render() // The modal's onClose is set to foldAnimInto from useHideLogic // Verify the hook provides this function expect(mockHideLogicState.foldAnimInto).toBeDefined() expect(typeof mockHideLogicState.foldAnimInto).toBe('function') }) it('should pass handleStartToInstall to ReadyToInstall', () => { render() fireEvent.click(screen.getByTestId('start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalledTimes(1) }) it('should pass setIsInstalling to ReadyToInstall', () => { render() fireEvent.click(screen.getByTestId('set-installing-true')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledWith(true) }) it('should pass setIsInstalling with false to ReadyToInstall', () => { render() fireEvent.click(screen.getByTestId('set-installing-false')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledWith(false) }) }) // ================================ // Title Logic Tests (getTitle callback) // ================================ describe('Title Logic (getTitle callback)', () => { it('should return uploadFailed title when step is uploadFailed', () => { render() fireEvent.click(screen.getByTestId('change-to-upload-failed')) expect(screen.getByText('plugin.installModal.uploadFailed')).toBeInTheDocument() }) it('should return installComplete title when step is installed', () => { render() fireEvent.click(screen.getByTestId('change-to-installed')) expect(screen.getByText('plugin.installModal.installComplete')).toBeInTheDocument() }) it('should return installPlugin title for all other steps', () => { render() // Default step - readyToInstall expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should return installPlugin title when step is uploading', () => { render() // Step is uploading expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) }) // ================================ // Component Memoization Tests // ================================ describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { // Verify that InstallBundle is memoized by checking its displayName or structure // Since the component is exported as React.memo(InstallBundle), we can check its type expect(InstallBundle).toBeDefined() expect(typeof InstallBundle).toBe('object') // memo returns an object }) it('should not re-render when same props are passed', () => { const onClose = vi.fn() const payload = createMockDependencies() const { rerender } = render( , ) // Re-render with same props reference rerender() // Component should still render correctly expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() }) }) // ================================ // User Interactions Tests // ================================ describe('User Interactions', () => { it('should handle start install button click', () => { render() fireEvent.click(screen.getByTestId('start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalledTimes(1) }) it('should handle close button click', () => { const onClose = vi.fn() render() fireEvent.click(screen.getByTestId('close-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) it('should handle step change to installed', () => { render() fireEvent.click(screen.getByTestId('change-to-installed')) expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.installed) expect(screen.getByText('plugin.installModal.installComplete')).toBeInTheDocument() }) it('should handle step change to uploadFailed', () => { render() fireEvent.click(screen.getByTestId('change-to-upload-failed')) expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.uploadFailed) expect(screen.getByText('plugin.installModal.uploadFailed')).toBeInTheDocument() }) }) // ================================ // Edge Cases Tests // ================================ describe('Edge Cases', () => { it('should handle empty dependencies array', () => { render() expect(screen.getByTestId('plugins-count')).toHaveTextContent('0') }) it('should handle large number of dependencies', () => { const largeDependencies: Dependency[] = Array.from({ length: 100 }, (_, i) => ({ type: 'marketplace', value: { marketplace_plugin_unique_identifier: `plugin-${i}-uid`, }, })) render() expect(screen.getByTestId('plugins-count')).toHaveTextContent('100') }) it('should handle dependencies with different types', () => { const mixedDependencies: Dependency[] = [ { type: 'marketplace', value: { marketplace_plugin_unique_identifier: 'mp-uid' } }, { type: 'github', value: { repo: 'org/repo', version: 'v1.0.0', package: 'pkg.zip' } }, { type: 'package', value: { unique_identifier: 'pkg-uid', manifest: { plugin_unique_identifier: 'pkg-uid', version: '1.0.0', author: 'author', icon: 'icon.png', name: 'Package', category: PluginCategoryEnum.tool, label: {} as Record, description: {} as Record, created_at: '', resource: {}, plugins: [], verified: true, endpoint: { settings: [], endpoints: [] }, model: null, tags: [], agent_strategy: null, meta: { version: '1.0.0' }, trigger: {} as PluginDeclaration['trigger'], }, }, }, ] render() expect(screen.getByTestId('plugins-count')).toHaveTextContent('3') }) it('should handle rapid step changes', () => { render() // Rapid step changes fireEvent.click(screen.getByTestId('change-to-installed')) fireEvent.click(screen.getByTestId('change-to-upload-failed')) fireEvent.click(screen.getByTestId('change-to-ready')) // Should end up at readyToInstall expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.readyToInstall) expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should handle multiple setIsInstalling calls', () => { render() fireEvent.click(screen.getByTestId('set-installing-true')) fireEvent.click(screen.getByTestId('set-installing-false')) fireEvent.click(screen.getByTestId('set-installing-true')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledTimes(3) expect(mockHideLogicState.setIsInstalling).toHaveBeenNthCalledWith(1, true) expect(mockHideLogicState.setIsInstalling).toHaveBeenNthCalledWith(2, false) expect(mockHideLogicState.setIsInstalling).toHaveBeenNthCalledWith(3, true) }) }) // ================================ // InstallType Enum Tests // ================================ describe('InstallType Enum', () => { it('should export InstallType enum with correct values', () => { expect(InstallType.fromLocal).toBe('fromLocal') expect(InstallType.fromMarketplace).toBe('fromMarketplace') expect(InstallType.fromDSL).toBe('fromDSL') }) it('should handle all InstallType values', () => { const types = [InstallType.fromLocal, InstallType.fromMarketplace, InstallType.fromDSL] types.forEach((type) => { const { unmount } = render( , ) expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() unmount() }) }) }) // ================================ // Modal Integration Tests // ================================ describe('Modal Integration', () => { it('should render modal with title', () => { render() // Verify modal renders with title expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should render modal with closable behavior', () => { render() // Modal should render the content including the ReadyToInstall component expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() }) it('should display title in modal header', () => { render() const titleElement = screen.getByText('plugin.installModal.installPlugin') expect(titleElement).toBeInTheDocument() expect(titleElement).toHaveClass('title-2xl-semi-bold') }) }) // ================================ // Initial Step Determination Tests // ================================ describe('Initial Step Determination', () => { it('should set initial step based on installType for fromMarketplace', () => { render() expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.readyToInstall) }) it('should set initial step based on installType for fromLocal', () => { render() expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.uploading) }) it('should set initial step based on installType for fromDSL', () => { render() expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.uploading) }) it('should use default installType when not provided', () => { render() // Default is fromMarketplace which results in readyToInstall expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.readyToInstall) }) }) // ================================ // useHideLogic Hook Integration Tests // ================================ describe('useHideLogic Hook Integration', () => { it('should receive modalClassName from useHideLogic', () => { mockHideLogicState.modalClassName = 'custom-modal-class' render() // Verify hook provides modalClassName (component uses it in Modal className prop) expect(mockHideLogicState.modalClassName).toBe('custom-modal-class') }) it('should pass onClose to useHideLogic', () => { const onClose = vi.fn() render() // The hook receives onClose and returns foldAnimInto // When modal closes, foldAnimInto should be used expect(mockHideLogicState.foldAnimInto).toBeDefined() }) it('should use foldAnimInto for modal close action', () => { render() // The modal's onClose is set to foldAnimInto // This is verified by checking that the hook returns the function expect(typeof mockHideLogicState.foldAnimInto).toBe('function') }) }) // ================================ // ReadyToInstall Props Passing Tests // ================================ describe('ReadyToInstall Props Passing', () => { it('should pass step to ReadyToInstall', () => { render() expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.readyToInstall) }) it('should pass onStepChange to ReadyToInstall', () => { render() // Trigger step change fireEvent.click(screen.getByTestId('change-to-installed')) expect(screen.getByTestId('current-step')).toHaveTextContent(InstallStep.installed) }) it('should pass onStartToInstall to ReadyToInstall', () => { render() fireEvent.click(screen.getByTestId('start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalled() }) it('should pass setIsInstalling to ReadyToInstall', () => { render() fireEvent.click(screen.getByTestId('set-installing-true')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledWith(true) }) it('should pass allPlugins (fromDSLPayload) to ReadyToInstall', () => { const plugins = createMockDependencies() render() expect(screen.getByTestId('plugins-count')).toHaveTextContent(String(plugins.length)) }) it('should pass onClose to ReadyToInstall', () => { const onClose = vi.fn() render() fireEvent.click(screen.getByTestId('close-btn')) expect(onClose).toHaveBeenCalled() }) }) // ================================ // Callback Memoization Tests // ================================ describe('Callback Memoization (getTitle)', () => { it('should return correct title based on current step', () => { render() // Default step (readyToInstall) -> installPlugin title expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should update title when step changes', () => { render() // Change to installed fireEvent.click(screen.getByTestId('change-to-installed')) expect(screen.getByText('plugin.installModal.installComplete')).toBeInTheDocument() // Change to uploadFailed fireEvent.click(screen.getByTestId('change-to-upload-failed')) expect(screen.getByText('plugin.installModal.uploadFailed')).toBeInTheDocument() // Change back to readyToInstall fireEvent.click(screen.getByTestId('change-to-ready')) expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) }) // ================================ // Error Handling Tests // ================================ describe('Error Handling', () => { it('should handle null in fromDSLPayload gracefully', () => { // TypeScript would catch this, but testing runtime behavior // @ts-expect-error Testing null handling render() // Should render without crashing, count will be 0 expect(screen.getByTestId('plugins-count')).toHaveTextContent('0') }) it('should handle undefined in fromDSLPayload gracefully', () => { // @ts-expect-error Testing undefined handling render() // Should render without crashing expect(screen.getByTestId('plugins-count')).toHaveTextContent('0') }) }) // ================================ // CSS Classes Tests // ================================ describe('CSS Classes', () => { it('should render modal with proper structure', () => { render() // Verify component renders with expected structure expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should apply correct CSS classes to title', () => { render() const title = screen.getByText('plugin.installModal.installPlugin') expect(title).toHaveClass('title-2xl-semi-bold') expect(title).toHaveClass('text-text-primary') }) }) // ================================ // Rendering Consistency Tests // ================================ describe('Rendering Consistency', () => { it('should render consistently across different installTypes', () => { // fromMarketplace const { unmount: unmount1 } = render( , ) expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() unmount1() // fromLocal const { unmount: unmount2 } = render( , ) expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() unmount2() // fromDSL const { unmount: unmount3 } = render( , ) expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() unmount3() }) it('should maintain modal structure across step changes', () => { render() // Check ReadyToInstall component exists expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() // Change step fireEvent.click(screen.getByTestId('change-to-installed')) // ReadyToInstall should still exist expect(screen.getByTestId('ready-to-install')).toBeInTheDocument() // Title should be updated expect(screen.getByText('plugin.installModal.installComplete')).toBeInTheDocument() }) }) }) // ================================================================ // ReadyToInstall Component Tests (using mocked version from InstallBundle) // ================================================================ describe('ReadyToInstall (via InstallBundle mock)', () => { // Note: ReadyToInstall is mocked for InstallBundle tests. // These tests verify the mock interface and component behavior. beforeEach(() => { vi.clearAllMocks() }) // ================================ // Component Definition Tests // ================================ describe('Component Definition', () => { it('should be defined and importable', () => { expect(ReadyToInstall).toBeDefined() }) it('should be a memoized component', () => { // The import gives us the mocked version, which is a function expect(typeof ReadyToInstall).toBe('function') }) }) }) // ================================================================ // Installed Component Tests // ================================================================ describe('Installed', () => { const defaultInstalledProps = { list: [createMockPlugin()], installStatus: [createMockInstallStatus()], onCancel: vi.fn(), } beforeEach(() => { vi.clearAllMocks() }) // ================================ // Rendering Tests // ================================ describe('Rendering', () => { it('should render plugin list', () => { render() // Should show close button expect(screen.getByRole('button', { name: 'common.operation.close' })).toBeInTheDocument() }) it('should render multiple plugins', () => { const plugins = [ createMockPlugin({ plugin_id: 'plugin-1', name: 'Plugin 1' }), createMockPlugin({ plugin_id: 'plugin-2', name: 'Plugin 2' }), ] const statuses = [ createMockInstallStatus({ success: true }), createMockInstallStatus({ success: false }), ] render() expect(screen.getByRole('button', { name: 'common.operation.close' })).toBeInTheDocument() }) it('should not render close button when isHideButton is true', () => { render() expect(screen.queryByRole('button', { name: 'common.operation.close' })).not.toBeInTheDocument() }) }) // ================================ // User Interactions Tests // ================================ describe('User Interactions', () => { it('should call onCancel when close button is clicked', () => { const onCancel = vi.fn() render() fireEvent.click(screen.getByRole('button', { name: 'common.operation.close' })) expect(onCancel).toHaveBeenCalledTimes(1) }) }) // ================================ // Edge Cases Tests // ================================ describe('Edge Cases', () => { it('should handle empty plugin list', () => { render() expect(screen.getByRole('button', { name: 'common.operation.close' })).toBeInTheDocument() }) it('should handle mixed install statuses', () => { const plugins = [ createMockPlugin({ plugin_id: 'success-plugin' }), createMockPlugin({ plugin_id: 'failed-plugin' }), ] const statuses = [ createMockInstallStatus({ success: true }), createMockInstallStatus({ success: false }), ] render() expect(screen.getByRole('button', { name: 'common.operation.close' })).toBeInTheDocument() }) }) // ================================ // Component Memoization Tests // ================================ describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { expect(Installed).toBeDefined() expect(typeof Installed).toBe('object') }) }) }) // ================================================================ // LoadedItem Component Tests // ================================================================ describe('LoadedItem', () => { const defaultLoadedItemProps = { checked: false, onCheckedChange: vi.fn(), payload: createMockPlugin(), versionInfo: createMockVersionProps(), } beforeEach(() => { vi.clearAllMocks() }) // Helper to find checkbox element const getCheckbox = () => screen.getByTestId(/^checkbox/) // ================================ // Rendering Tests // ================================ describe('Rendering', () => { it('should render checkbox', () => { render() expect(getCheckbox()).toBeInTheDocument() }) it('should render checkbox with check icon when checked prop is true', () => { render() expect(getCheckbox()).toBeInTheDocument() // Check icon should be present when checked expect(screen.getByTestId(/^check-icon/)).toBeInTheDocument() }) it('should render checkbox without check icon when checked prop is false', () => { render() expect(getCheckbox()).toBeInTheDocument() // Check icon should not be present when unchecked expect(screen.queryByTestId(/^check-icon/)).not.toBeInTheDocument() }) }) // ================================ // User Interactions Tests // ================================ describe('User Interactions', () => { it('should call onCheckedChange when checkbox is clicked', () => { const onCheckedChange = vi.fn() render() fireEvent.click(getCheckbox()) expect(onCheckedChange).toHaveBeenCalledWith(defaultLoadedItemProps.payload) }) }) // ================================ // Props Tests // ================================ describe('Props', () => { it('should handle isFromMarketPlace prop', () => { render() expect(getCheckbox()).toBeInTheDocument() }) it('should display version info when payload has version', () => { const pluginWithVersion = createMockPlugin({ version: '2.0.0' }) render() expect(getCheckbox()).toBeInTheDocument() }) }) // ================================ // Component Memoization Tests // ================================ describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { expect(LoadedItem).toBeDefined() expect(typeof LoadedItem).toBe('object') }) }) }) // ================================================================ // MarketplaceItem Component Tests // ================================================================ describe('MarketplaceItem', () => { const defaultMarketplaceItemProps = { checked: false, onCheckedChange: vi.fn(), payload: createMockPlugin(), version: '1.0.0', versionInfo: createMockVersionProps(), } beforeEach(() => { vi.clearAllMocks() }) // Helper to find checkbox element const getCheckbox = () => screen.getByTestId(/^checkbox/) // ================================ // Rendering Tests // ================================ describe('Rendering', () => { it('should render LoadedItem when payload is provided', () => { render() expect(getCheckbox()).toBeInTheDocument() }) it('should render Loading when payload is undefined', () => { render() // Loading component renders a disabled checkbox const checkbox = screen.getByTestId(/^checkbox/) expect(checkbox).toHaveClass('cursor-not-allowed') }) }) // ================================ // Props Tests // ================================ describe('Props', () => { it('should pass version to LoadedItem', () => { render() expect(getCheckbox()).toBeInTheDocument() }) it('should pass checked state to LoadedItem', () => { render() // When checked, the check icon should be present expect(screen.getByTestId(/^check-icon/)).toBeInTheDocument() }) }) // ================================ // User Interactions Tests // ================================ describe('User Interactions', () => { it('should call onCheckedChange when clicked', () => { const onCheckedChange = vi.fn() render() fireEvent.click(getCheckbox()) expect(onCheckedChange).toHaveBeenCalled() }) }) // ================================ // Component Memoization Tests // ================================ describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { expect(MarketplaceItem).toBeDefined() expect(typeof MarketplaceItem).toBe('object') }) }) }) // ================================================================ // PackageItem Component Tests // ================================================================ describe('PackageItem', () => { const defaultPackageItemProps = { checked: false, onCheckedChange: vi.fn(), payload: createMockPackageDependency(), versionInfo: createMockVersionProps(), } beforeEach(() => { vi.clearAllMocks() }) // Helper to find checkbox element const getCheckbox = () => screen.getByTestId(/^checkbox/) // ================================ // Rendering Tests // ================================ describe('Rendering', () => { it('should render LoadedItem when payload has manifest', () => { render() expect(getCheckbox()).toBeInTheDocument() }) it('should render LoadingError when manifest is missing', () => { const invalidPayload = { type: 'package', value: { unique_identifier: 'test' }, } as PackageDependency render() // LoadingError renders a disabled checkbox and error text const checkbox = screen.getByTestId(/^checkbox/) expect(checkbox).toHaveClass('cursor-not-allowed') expect(screen.getByText('plugin.installModal.pluginLoadError')).toBeInTheDocument() }) }) // ================================ // Props Tests // ================================ describe('Props', () => { it('should pass isFromMarketPlace to LoadedItem', () => { render() expect(getCheckbox()).toBeInTheDocument() }) it('should pass checked state to LoadedItem', () => { render() // When checked, the check icon should be present expect(screen.getByTestId(/^check-icon/)).toBeInTheDocument() }) }) // ================================ // User Interactions Tests // ================================ describe('User Interactions', () => { it('should call onCheckedChange when clicked', () => { const onCheckedChange = vi.fn() render() fireEvent.click(getCheckbox()) expect(onCheckedChange).toHaveBeenCalled() }) }) // ================================ // Component Memoization Tests // ================================ describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { expect(PackageItem).toBeDefined() expect(typeof PackageItem).toBe('object') }) }) }) // ================================================================ // GithubItem Component Tests // ================================================================ describe('GithubItem', () => { const defaultGithubItemProps = { checked: false, onCheckedChange: vi.fn(), dependency: createMockGitHubDependency(), versionInfo: createMockVersionProps(), onFetchedPayload: vi.fn(), onFetchError: vi.fn(), } beforeEach(() => { vi.clearAllMocks() mockUseUploadGitHub.mockReturnValue({ data: null, error: null }) }) // ================================ // Rendering Tests // ================================ describe('Rendering', () => { it('should render Loading when data is not yet fetched', () => { mockUseUploadGitHub.mockReturnValue({ data: null, error: null }) render() // Loading component renders a disabled checkbox const checkbox = screen.getByTestId(/^checkbox/) expect(checkbox).toHaveClass('cursor-not-allowed') }) it('should render LoadedItem when data is fetched', async () => { const mockData = { unique_identifier: 'test-uid', manifest: { plugin_unique_identifier: 'test-uid', version: '1.0.0', author: 'test-author', icon: 'icon.png', name: 'Test Plugin', category: PluginCategoryEnum.tool, label: { 'en-US': 'Test' }, description: { 'en-US': 'Test Description' }, created_at: '2024-01-01', resource: {}, plugins: [], verified: true, endpoint: { settings: [], endpoints: [] }, model: null, tags: [], agent_strategy: null, meta: { version: '1.0.0' }, trigger: {}, }, } mockUseUploadGitHub.mockReturnValue({ data: mockData, error: null }) render() // When data is loaded, LoadedItem should be rendered with checkbox await waitFor(() => { expect(screen.getByTestId(/^checkbox/)).toBeInTheDocument() }) }) }) // ================================ // Callback Tests // ================================ describe('Callbacks', () => { it('should call onFetchedPayload when data is fetched', async () => { const onFetchedPayload = vi.fn() const mockData = { unique_identifier: 'test-uid', manifest: { plugin_unique_identifier: 'test-uid', version: '1.0.0', author: 'test-author', icon: 'icon.png', name: 'Test Plugin', category: PluginCategoryEnum.tool, label: { 'en-US': 'Test' }, description: { 'en-US': 'Test Description' }, created_at: '2024-01-01', resource: {}, plugins: [], verified: true, endpoint: { settings: [], endpoints: [] }, model: null, tags: [], agent_strategy: null, meta: { version: '1.0.0' }, trigger: {}, }, } mockUseUploadGitHub.mockReturnValue({ data: mockData, error: null }) render() await waitFor(() => { expect(onFetchedPayload).toHaveBeenCalled() }) }) it('should call onFetchError when error occurs', async () => { const onFetchError = vi.fn() mockUseUploadGitHub.mockReturnValue({ data: null, error: new Error('Fetch failed') }) render() await waitFor(() => { expect(onFetchError).toHaveBeenCalled() }) }) }) // ================================ // Props Tests // ================================ describe('Props', () => { it('should pass dependency info to useUploadGitHub', () => { const dependency = createMockGitHubDependency() render() expect(mockUseUploadGitHub).toHaveBeenCalledWith({ repo: dependency.value.repo, version: dependency.value.version, package: dependency.value.package, }) }) }) // ================================ // Component Memoization Tests // ================================ describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { expect(GithubItem).toBeDefined() expect(typeof GithubItem).toBe('object') }) }) })