import type { Dependency, PluginDeclaration } from '../../types' import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { InstallStep, PluginCategoryEnum } from '../../types' import InstallFromLocalPackage from './index' // Factory functions for test data const createMockManifest = (overrides: Partial = {}): PluginDeclaration => ({ plugin_unique_identifier: 'test-plugin-uid', version: '1.0.0', author: 'test-author', icon: 'test-icon.png', name: 'Test Plugin', category: PluginCategoryEnum.tool, label: { 'en-US': 'Test Plugin' } as PluginDeclaration['label'], description: { 'en-US': 'A test plugin' } as PluginDeclaration['description'], created_at: '2024-01-01T00:00:00Z', resource: {}, plugins: [], verified: true, endpoint: { settings: [], endpoints: [] }, model: null, tags: [], agent_strategy: null, meta: { version: '1.0.0' }, trigger: {} as PluginDeclaration['trigger'], ...overrides, }) const createMockDependencies = (): Dependency[] => [ { type: 'package', value: { unique_identifier: 'dep-1', manifest: createMockManifest({ name: 'Dep Plugin 1' }), }, }, { type: 'package', value: { unique_identifier: 'dep-2', manifest: createMockManifest({ name: 'Dep Plugin 2' }), }, }, ] const createMockFile = (name: string = 'test-plugin.difypkg'): File => { return new File(['test content'], name, { type: 'application/octet-stream' }) } const createMockBundleFile = (): File => { return new File(['bundle content'], 'test-bundle.difybndl', { type: 'application/octet-stream' }) } // Mock external dependencies const mockGetIconUrl = vi.fn() vi.mock('@/app/components/plugins/install-plugin/base/use-get-icon', () => ({ default: () => ({ getIconUrl: mockGetIconUrl }), })) let mockHideLogicState = { modalClassName: 'test-modal-class', foldAnimInto: vi.fn(), setIsInstalling: vi.fn(), handleStartToInstall: vi.fn(), } vi.mock('../hooks/use-hide-logic', () => ({ default: () => mockHideLogicState, })) // Mock child components let uploadingOnPackageUploaded: ((result: { uniqueIdentifier: string, manifest: PluginDeclaration }) => void) | null = null let uploadingOnBundleUploaded: ((result: Dependency[]) => void) | null = null let _uploadingOnFailed: ((errorMsg: string) => void) | null = null vi.mock('./steps/uploading', () => ({ default: ({ isBundle, file, onCancel, onPackageUploaded, onBundleUploaded, onFailed, }: { isBundle: boolean file: File onCancel: () => void onPackageUploaded: (result: { uniqueIdentifier: string, manifest: PluginDeclaration }) => void onBundleUploaded: (result: Dependency[]) => void onFailed: (errorMsg: string) => void }) => { uploadingOnPackageUploaded = onPackageUploaded uploadingOnBundleUploaded = onBundleUploaded _uploadingOnFailed = onFailed return (
{isBundle ? 'true' : 'false'} {file.name}
) }, })) let _packageStepChangeCallback: ((step: InstallStep) => void) | null = null let _packageSetIsInstallingCallback: ((isInstalling: boolean) => void) | null = null let _packageOnErrorCallback: ((errorMsg: string) => void) | null = null vi.mock('./ready-to-install', () => ({ default: ({ step, onStepChange, onStartToInstall, setIsInstalling, onClose, uniqueIdentifier, manifest, errorMsg, onError, }: { step: InstallStep onStepChange: (step: InstallStep) => void onStartToInstall: () => void setIsInstalling: (isInstalling: boolean) => void onClose: () => void uniqueIdentifier: string | null manifest: PluginDeclaration | null errorMsg: string | null onError: (errorMsg: string) => void }) => { _packageStepChangeCallback = onStepChange _packageSetIsInstallingCallback = setIsInstalling _packageOnErrorCallback = onError return (
{step} {uniqueIdentifier || 'null'} {manifest?.name || 'null'} {errorMsg || 'null'}
) }, })) let _bundleStepChangeCallback: ((step: InstallStep) => void) | null = null let _bundleSetIsInstallingCallback: ((isInstalling: boolean) => void) | null = null vi.mock('../install-bundle/ready-to-install', () => ({ default: ({ step, onStepChange, onStartToInstall, setIsInstalling, onClose, allPlugins, }: { step: InstallStep onStepChange: (step: InstallStep) => void onStartToInstall: () => void setIsInstalling: (isInstalling: boolean) => void onClose: () => void allPlugins: Dependency[] }) => { _bundleStepChangeCallback = onStepChange _bundleSetIsInstallingCallback = setIsInstalling return (
{step} {allPlugins.length}
) }, })) describe('InstallFromLocalPackage', () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } beforeEach(() => { vi.clearAllMocks() mockGetIconUrl.mockReturnValue('processed-icon-url') mockHideLogicState = { modalClassName: 'test-modal-class', foldAnimInto: vi.fn(), setIsInstalling: vi.fn(), handleStartToInstall: vi.fn(), } uploadingOnPackageUploaded = null uploadingOnBundleUploaded = null _uploadingOnFailed = null _packageStepChangeCallback = null _packageSetIsInstallingCallback = null _packageOnErrorCallback = null _bundleStepChangeCallback = null _bundleSetIsInstallingCallback = null }) // ================================ // Rendering Tests // ================================ describe('Rendering', () => { it('should render modal with uploading step initially', () => { render() expect(screen.getByTestId('uploading-step')).toBeInTheDocument() expect(screen.getByTestId('file-name')).toHaveTextContent('test-plugin.difypkg') }) it('should render with correct modal title for uploading step', () => { render() expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should apply modal className from useHideLogic', () => { expect(mockHideLogicState.modalClassName).toBe('test-modal-class') }) it('should identify bundle file correctly', () => { render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('true') }) it('should identify package file correctly', () => { render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('false') }) }) // ================================ // Title Display Tests // ================================ describe('Title Display', () => { it('should show install plugin title initially', () => { render() expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() }) it('should show upload failed title when upload fails', async () => { render() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByText('plugin.installModal.uploadFailed')).toBeInTheDocument() }) }) it('should show installed successfully title for package when installed', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-installed-btn')) await waitFor(() => { expect(screen.getByText('plugin.installModal.installedSuccessfully')).toBeInTheDocument() }) }) it('should show install complete title for bundle when installed', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-step-installed-btn')) await waitFor(() => { expect(screen.getByText('plugin.installModal.installComplete')).toBeInTheDocument() }) }) it('should show install failed title when install fails', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-failed-btn')) await waitFor(() => { expect(screen.getByText('plugin.installModal.installFailed')).toBeInTheDocument() }) }) }) // ================================ // State Management Tests // ================================ describe('State Management', () => { it('should transition from uploading to readyToInstall on successful package upload', async () => { render() expect(screen.getByTestId('uploading-step')).toBeInTheDocument() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() expect(screen.getByTestId('package-step')).toHaveTextContent('readyToInstall') }) }) it('should transition from uploading to readyToInstall on successful bundle upload', async () => { render() expect(screen.getByTestId('uploading-step')).toBeInTheDocument() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() expect(screen.getByTestId('bundle-step')).toHaveTextContent('readyToInstall') }) }) it('should transition to uploadFailed step on upload error', async () => { render() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() expect(screen.getByTestId('package-step')).toHaveTextContent('uploadFailed') }) }) it('should store uniqueIdentifier after package upload', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-unique-identifier')).toHaveTextContent('test-unique-id') }) }) it('should store manifest after package upload', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-manifest-name')).toHaveTextContent('Test Plugin') }) }) it('should store error message after upload failure', async () => { render() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Upload failed error') }) }) it('should store dependencies after bundle upload', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-plugins-count')).toHaveTextContent('2') }) }) }) // ================================ // Icon Processing Tests // ================================ describe('Icon Processing', () => { it('should process icon URL on successful package upload', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(mockGetIconUrl).toHaveBeenCalledWith('test-icon.png') }) }) it('should process dark icon URL if provided', async () => { const manifestWithDarkIcon = createMockManifest({ icon_dark: 'test-icon-dark.png' }) render() // Manually call the callback with dark icon manifest if (uploadingOnPackageUploaded) { uploadingOnPackageUploaded({ uniqueIdentifier: 'test-id', manifest: manifestWithDarkIcon, }) } await waitFor(() => { expect(mockGetIconUrl).toHaveBeenCalledWith('test-icon.png') expect(mockGetIconUrl).toHaveBeenCalledWith('test-icon-dark.png') }) }) it('should not process dark icon if not provided', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(mockGetIconUrl).toHaveBeenCalledTimes(1) expect(mockGetIconUrl).toHaveBeenCalledWith('test-icon.png') }) }) }) // ================================ // Callback Tests // ================================ describe('Callbacks', () => { it('should call onClose when cancel button is clicked during upload', () => { render() fireEvent.click(screen.getByTestId('cancel-upload-btn')) expect(defaultProps.onClose).toHaveBeenCalledTimes(1) }) it('should call foldAnimInto when modal close is triggered', () => { render() expect(mockHideLogicState.foldAnimInto).toBeDefined() }) it('should call handleStartToInstall when start install is triggered for package', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalledTimes(1) }) it('should call handleStartToInstall when start install is triggered for bundle', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalledTimes(1) }) it('should call onClose when close button is clicked in package ready-to-install', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-close-btn')) expect(defaultProps.onClose).toHaveBeenCalledTimes(1) }) it('should call onClose when close button is clicked in bundle ready-to-install', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-close-btn')) expect(defaultProps.onClose).toHaveBeenCalledTimes(1) }) }) // ================================ // Callback Stability Tests (Memoization) // ================================ describe('Callback Stability', () => { it('should maintain stable handlePackageUploaded callback reference', async () => { const { rerender } = render() expect(screen.getByTestId('uploading-step')).toBeInTheDocument() // Rerender with same props rerender() // The component should still work correctly fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) }) it('should maintain stable handleBundleUploaded callback reference', async () => { const bundleProps = { ...defaultProps, file: createMockBundleFile() } const { rerender } = render() expect(screen.getByTestId('uploading-step')).toBeInTheDocument() // Rerender with same props rerender() // The component should still work correctly fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) }) it('should maintain stable handleUploadFail callback reference', async () => { const { rerender } = render() // Rerender with same props rerender() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Upload failed error') }) }) }) // ================================ // Step Change Tests // ================================ describe('Step Change Handling', () => { it('should allow step change to installed for package', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('installed') }) }) it('should allow step change to installFailed for package', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-failed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('failed') }) }) it('should allow step change to installed for bundle', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-step')).toHaveTextContent('installed') }) }) it('should allow step change to installFailed for bundle', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-step-failed-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-step')).toHaveTextContent('failed') }) }) }) // ================================ // setIsInstalling Tests // ================================ describe('setIsInstalling Handling', () => { it('should pass setIsInstalling to package ready-to-install', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-set-installing-false-btn')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledWith(false) }) it('should pass setIsInstalling to bundle ready-to-install', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-set-installing-false-btn')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledWith(false) }) }) // ================================ // Error Handling Tests // ================================ describe('Error Handling', () => { it('should handle onError callback for package', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-set-error-btn')) await waitFor(() => { expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Custom error message') }) }) it('should preserve error message through step changes', async () => { render() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Upload failed error') }) // Error message should still be accessible expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Upload failed error') }) }) // ================================ // Edge Cases Tests // ================================ describe('Edge Cases', () => { it('should handle file with .difypkg extension as package', () => { const pkgFile = createMockFile('my-plugin.difypkg') render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('false') }) it('should handle file with .difybndl extension as bundle', () => { const bundleFile = createMockFile('my-bundle.difybndl') render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('true') }) it('should handle file without standard extension as package', () => { const otherFile = createMockFile('plugin.zip') render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('false') }) it('should handle empty dependencies array for bundle', async () => { render() // Manually trigger with empty dependencies if (uploadingOnBundleUploaded) { uploadingOnBundleUploaded([]) } await waitFor(() => { expect(screen.getByTestId('bundle-plugins-count')).toHaveTextContent('0') }) }) it('should handle manifest without icon_dark', async () => { const manifestWithoutDarkIcon = createMockManifest({ icon_dark: undefined }) render() if (uploadingOnPackageUploaded) { uploadingOnPackageUploaded({ uniqueIdentifier: 'test-id', manifest: manifestWithoutDarkIcon, }) } await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) // Should only call getIconUrl once for the main icon expect(mockGetIconUrl).toHaveBeenCalledTimes(1) }) it('should display correct file name in uploading step', () => { const customFile = createMockFile('custom-plugin-name.difypkg') render() expect(screen.getByTestId('file-name')).toHaveTextContent('custom-plugin-name.difypkg') }) it('should handle rapid state transitions', async () => { render() // Quickly trigger upload success fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) // Quickly trigger step changes fireEvent.click(screen.getByTestId('package-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('installed') }) }) }) // ================================ // Conditional Rendering Tests // ================================ describe('Conditional Rendering', () => { it('should show uploading step initially and hide after upload', async () => { render() expect(screen.getByTestId('uploading-step')).toBeInTheDocument() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.queryByTestId('uploading-step')).not.toBeInTheDocument() expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) }) it('should render ReadyToInstallPackage for package files', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() expect(screen.queryByTestId('ready-to-install-bundle')).not.toBeInTheDocument() }) }) it('should render ReadyToInstallBundle for bundle files', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() expect(screen.queryByTestId('ready-to-install-package')).not.toBeInTheDocument() }) }) it('should render both uploading and ready-to-install simultaneously during transition', async () => { render() // Initially only uploading is shown expect(screen.getByTestId('uploading-step')).toBeInTheDocument() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) // After upload, only ready-to-install is shown await waitFor(() => { expect(screen.queryByTestId('uploading-step')).not.toBeInTheDocument() expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) }) }) // ================================ // Data Flow Tests // ================================ describe('Data Flow', () => { it('should pass correct uniqueIdentifier to ReadyToInstallPackage', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-unique-identifier')).toHaveTextContent('test-unique-id') }) }) it('should pass processed manifest to ReadyToInstallPackage', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-manifest-name')).toHaveTextContent('Test Plugin') }) }) it('should pass all dependencies to ReadyToInstallBundle', async () => { render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-plugins-count')).toHaveTextContent('2') }) }) it('should pass error message to ReadyToInstallPackage', async () => { render() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Upload failed error') }) }) it('should pass null uniqueIdentifier when not uploaded for package', () => { render() // Before upload, uniqueIdentifier should be null // The uploading step is shown, so ReadyToInstallPackage is not rendered yet expect(screen.getByTestId('uploading-step')).toBeInTheDocument() }) it('should pass null manifest when not uploaded for package', () => { render() // Before upload, manifest should be null // The uploading step is shown, so ReadyToInstallPackage is not rendered yet expect(screen.getByTestId('uploading-step')).toBeInTheDocument() }) }) // ================================ // Prop Variations Tests // ================================ describe('Prop Variations', () => { it('should work with different file names', () => { const files = [ createMockFile('plugin-a.difypkg'), createMockFile('plugin-b.difypkg'), createMockFile('bundle-c.difybndl'), ] files.forEach((file) => { const { unmount } = render() expect(screen.getByTestId('file-name')).toHaveTextContent(file.name) unmount() }) }) it('should call different onClose handlers correctly', () => { const onClose1 = vi.fn() const onClose2 = vi.fn() const { rerender } = render() fireEvent.click(screen.getByTestId('cancel-upload-btn')) expect(onClose1).toHaveBeenCalledTimes(1) expect(onClose2).not.toHaveBeenCalled() rerender() fireEvent.click(screen.getByTestId('cancel-upload-btn')) expect(onClose2).toHaveBeenCalledTimes(1) }) it('should handle different file types correctly', () => { // Package file const { rerender } = render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('false') // Bundle file rerender() expect(screen.getByTestId('is-bundle')).toHaveTextContent('true') }) }) // ================================ // getTitle Callback Tests // ================================ describe('getTitle Callback', () => { it('should return correct title for all InstallStep values', async () => { render() // uploading step - shows installPlugin expect(screen.getByText('plugin.installModal.installPlugin')).toBeInTheDocument() // uploadFailed step fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByText('plugin.installModal.uploadFailed')).toBeInTheDocument() }) }) it('should differentiate bundle and package installed titles', async () => { // Package installed title const { unmount } = render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-installed-btn')) await waitFor(() => { expect(screen.getByText('plugin.installModal.installedSuccessfully')).toBeInTheDocument() }) // Unmount and create fresh instance for bundle unmount() // Bundle installed title render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-step-installed-btn')) await waitFor(() => { expect(screen.getByText('plugin.installModal.installComplete')).toBeInTheDocument() }) }) }) // ================================ // Integration with useHideLogic Tests // ================================ describe('Integration with useHideLogic', () => { it('should use modalClassName from useHideLogic', () => { render() // The hook is called and provides modalClassName expect(mockHideLogicState.modalClassName).toBe('test-modal-class') }) it('should use foldAnimInto as modal onClose handler', () => { render() // The foldAnimInto function is available from the hook expect(mockHideLogicState.foldAnimInto).toBeDefined() }) it('should use handleStartToInstall from useHideLogic', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalled() }) it('should use setIsInstalling from useHideLogic', async () => { render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-set-installing-false-btn')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledWith(false) }) }) // ================================ // useGetIcon Integration Tests // ================================ describe('Integration with useGetIcon', () => { it('should call getIconUrl when processing manifest icon', async () => { mockGetIconUrl.mockReturnValue('https://example.com/icon.png') render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(mockGetIconUrl).toHaveBeenCalledWith('test-icon.png') }) }) it('should handle getIconUrl for both icon and icon_dark', async () => { mockGetIconUrl.mockReturnValue('https://example.com/icon.png') render() const manifestWithDarkIcon = createMockManifest({ icon: 'light-icon.png', icon_dark: 'dark-icon.png', }) if (uploadingOnPackageUploaded) { uploadingOnPackageUploaded({ uniqueIdentifier: 'test-id', manifest: manifestWithDarkIcon, }) } await waitFor(() => { expect(mockGetIconUrl).toHaveBeenCalledWith('light-icon.png') expect(mockGetIconUrl).toHaveBeenCalledWith('dark-icon.png') }) }) }) }) // ================================================================ // ReadyToInstall Component Tests // ================================================================ describe('ReadyToInstall', () => { // Import the actual ReadyToInstall component for isolated testing // We'll test it through the parent component with specific scenarios const mockRefreshPluginList = vi.fn() // Reset mocks for ReadyToInstall tests beforeEach(() => { vi.clearAllMocks() mockRefreshPluginList.mockClear() }) describe('Step Conditional Rendering', () => { it('should render Install component when step is readyToInstall', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() // Trigger package upload to transition to readyToInstall step fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() expect(screen.getByTestId('package-step')).toHaveTextContent('readyToInstall') }) }) it('should render Installed component when step is uploadFailed', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() // Trigger upload failure fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('uploadFailed') }) }) it('should render Installed component when step is installed', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() // Trigger package upload then install fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('installed') }) }) it('should render Installed component when step is installFailed', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() // Trigger package upload then fail fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-failed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('failed') }) }) }) describe('handleInstalled Callback', () => { it('should transition to installed step when handleInstalled is called', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) // Simulate successful installation fireEvent.click(screen.getByTestId('package-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('installed') }) }) it('should call setIsInstalling(false) when installation completes', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-set-installing-false-btn')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledWith(false) }) }) describe('handleFailed Callback', () => { it('should transition to installFailed step when handleFailed is called', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-failed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('failed') }) }) it('should store error message when handleFailed is called with errorMsg', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-set-error-btn')) await waitFor(() => { expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Custom error message') }) }) }) describe('onClose Handler', () => { it('should call onClose when cancel is clicked', async () => { const onClose = vi.fn() const defaultProps = { file: createMockFile(), onClose, onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-close-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) }) describe('Props Passing', () => { it('should pass uniqueIdentifier to Install component', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-unique-identifier')).toHaveTextContent('test-unique-id') }) }) it('should pass manifest to Install component', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-manifest-name')).toHaveTextContent('Test Plugin') }) }) it('should pass errorMsg to Installed component', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Upload failed error') }) }) }) }) // ================================================================ // Uploading Step Component Tests // ================================================================ describe('Uploading Step', () => { beforeEach(() => { vi.clearAllMocks() mockGetIconUrl.mockReturnValue('processed-icon-url') mockHideLogicState = { modalClassName: 'test-modal-class', foldAnimInto: vi.fn(), setIsInstalling: vi.fn(), handleStartToInstall: vi.fn(), } }) describe('Rendering', () => { it('should render uploading state with file name', () => { const defaultProps = { file: createMockFile('my-custom-plugin.difypkg'), onClose: vi.fn(), onSuccess: vi.fn(), } render() expect(screen.getByTestId('uploading-step')).toBeInTheDocument() expect(screen.getByTestId('file-name')).toHaveTextContent('my-custom-plugin.difypkg') }) it('should pass isBundle=true for bundle files', () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('true') }) it('should pass isBundle=false for package files', () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('false') }) }) describe('Upload Callbacks', () => { it('should call onPackageUploaded with correct data for package files', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-unique-identifier')).toHaveTextContent('test-unique-id') expect(screen.getByTestId('package-manifest-name')).toHaveTextContent('Test Plugin') }) }) it('should call onBundleUploaded with dependencies for bundle files', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-plugins-count')).toHaveTextContent('2') }) }) it('should call onFailed with error message when upload fails', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Upload failed error') }) }) }) describe('Cancel Button', () => { it('should call onCancel when cancel button is clicked', () => { const onClose = vi.fn() const defaultProps = { file: createMockFile(), onClose, onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('cancel-upload-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) }) describe('File Type Detection', () => { it('should detect .difypkg as package', () => { const defaultProps = { file: createMockFile('test.difypkg'), onClose: vi.fn(), onSuccess: vi.fn(), } render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('false') }) it('should detect .difybndl as bundle', () => { const defaultProps = { file: createMockFile('test.difybndl'), onClose: vi.fn(), onSuccess: vi.fn(), } render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('true') }) it('should detect other extensions as package', () => { const defaultProps = { file: createMockFile('test.zip'), onClose: vi.fn(), onSuccess: vi.fn(), } render() expect(screen.getByTestId('is-bundle')).toHaveTextContent('false') }) }) }) // ================================================================ // Install Step Component Tests // ================================================================ describe('Install Step', () => { beforeEach(() => { vi.clearAllMocks() mockGetIconUrl.mockReturnValue('processed-icon-url') mockHideLogicState = { modalClassName: 'test-modal-class', foldAnimInto: vi.fn(), setIsInstalling: vi.fn(), handleStartToInstall: vi.fn(), } }) describe('Props Handling', () => { it('should receive uniqueIdentifier prop correctly', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-unique-identifier')).toHaveTextContent('test-unique-id') }) }) it('should receive payload prop correctly', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('package-manifest-name')).toHaveTextContent('Test Plugin') }) }) }) describe('Installation Callbacks', () => { it('should call onStartToInstall when install starts', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalledTimes(1) }) it('should call onInstalled when installation succeeds', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('installed') }) }) it('should call onFailed when installation fails', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-step-failed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('failed') }) }) }) describe('Cancel Handling', () => { it('should call onCancel when cancel is clicked', async () => { const onClose = vi.fn() const defaultProps = { file: createMockFile(), onClose, onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-close-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) }) }) // ================================================================ // Bundle ReadyToInstall Component Tests // ================================================================ describe('Bundle ReadyToInstall', () => { beforeEach(() => { vi.clearAllMocks() mockGetIconUrl.mockReturnValue('processed-icon-url') mockHideLogicState = { modalClassName: 'test-modal-class', foldAnimInto: vi.fn(), setIsInstalling: vi.fn(), handleStartToInstall: vi.fn(), } }) describe('Rendering', () => { it('should render bundle install view with all plugins', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() expect(screen.getByTestId('bundle-plugins-count')).toHaveTextContent('2') }) }) }) describe('Step Changes', () => { it('should transition to installed step on successful bundle install', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-step')).toHaveTextContent('installed') }) }) it('should transition to installFailed step on bundle install failure', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-step-failed-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-step')).toHaveTextContent('failed') }) }) }) describe('Callbacks', () => { it('should call onStartToInstall when bundle install starts', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalledTimes(1) }) it('should call setIsInstalling when bundle installation state changes', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-set-installing-false-btn')) expect(mockHideLogicState.setIsInstalling).toHaveBeenCalledWith(false) }) it('should call onClose when bundle install is cancelled', async () => { const onClose = vi.fn() const defaultProps = { file: createMockBundleFile(), onClose, onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-close-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) }) describe('Dependencies Handling', () => { it('should pass all dependencies to bundle install component', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-plugins-count')).toHaveTextContent('2') }) }) it('should handle empty dependencies array', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() // Manually trigger with empty dependencies const callback = uploadingOnBundleUploaded if (callback) { act(() => { callback([]) }) } await waitFor(() => { expect(screen.getByTestId('bundle-plugins-count')).toHaveTextContent('0') }) }) }) }) // ================================================================ // Complete Flow Integration Tests // ================================================================ describe('Complete Installation Flows', () => { beforeEach(() => { vi.clearAllMocks() mockGetIconUrl.mockReturnValue('processed-icon-url') mockHideLogicState = { modalClassName: 'test-modal-class', foldAnimInto: vi.fn(), setIsInstalling: vi.fn(), handleStartToInstall: vi.fn(), } }) describe('Package Installation Flow', () => { it('should complete full package installation flow: upload -> install -> success', async () => { const onClose = vi.fn() const onSuccess = vi.fn() const defaultProps = { file: createMockFile(), onClose, onSuccess } render() // Step 1: Uploading expect(screen.getByTestId('uploading-step')).toBeInTheDocument() // Step 2: Upload complete, transition to readyToInstall fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() expect(screen.getByTestId('package-step')).toHaveTextContent('readyToInstall') }) // Step 3: Start installation fireEvent.click(screen.getByTestId('package-start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalled() // Step 4: Installation complete fireEvent.click(screen.getByTestId('package-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('installed') expect(screen.getByText('plugin.installModal.installedSuccessfully')).toBeInTheDocument() }) }) it('should handle package installation failure flow', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() // Upload fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) // Set error and fail fireEvent.click(screen.getByTestId('package-set-error-btn')) fireEvent.click(screen.getByTestId('package-step-failed-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('failed') expect(screen.getByText('plugin.installModal.installFailed')).toBeInTheDocument() }) }) it('should handle upload failure flow', async () => { const defaultProps = { file: createMockFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-upload-fail-btn')) await waitFor(() => { expect(screen.getByTestId('package-step')).toHaveTextContent('uploadFailed') expect(screen.getByTestId('package-error-msg')).toHaveTextContent('Upload failed error') expect(screen.getByText('plugin.installModal.uploadFailed')).toBeInTheDocument() }) }) }) describe('Bundle Installation Flow', () => { it('should complete full bundle installation flow: upload -> install -> success', async () => { const onClose = vi.fn() const onSuccess = vi.fn() const defaultProps = { file: createMockBundleFile(), onClose, onSuccess } render() // Step 1: Uploading expect(screen.getByTestId('uploading-step')).toBeInTheDocument() expect(screen.getByTestId('is-bundle')).toHaveTextContent('true') // Step 2: Upload complete, transition to readyToInstall fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() expect(screen.getByTestId('bundle-step')).toHaveTextContent('readyToInstall') expect(screen.getByTestId('bundle-plugins-count')).toHaveTextContent('2') }) // Step 3: Start installation fireEvent.click(screen.getByTestId('bundle-start-install-btn')) expect(mockHideLogicState.handleStartToInstall).toHaveBeenCalled() // Step 4: Installation complete fireEvent.click(screen.getByTestId('bundle-step-installed-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-step')).toHaveTextContent('installed') expect(screen.getByText('plugin.installModal.installComplete')).toBeInTheDocument() }) }) it('should handle bundle installation failure flow', async () => { const defaultProps = { file: createMockBundleFile(), onClose: vi.fn(), onSuccess: vi.fn(), } render() // Upload fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) // Fail fireEvent.click(screen.getByTestId('bundle-step-failed-btn')) await waitFor(() => { expect(screen.getByTestId('bundle-step')).toHaveTextContent('failed') expect(screen.getByText('plugin.installModal.installFailed')).toBeInTheDocument() }) }) }) describe('User Cancellation Flows', () => { it('should allow cancellation during upload', () => { const onClose = vi.fn() const defaultProps = { file: createMockFile(), onClose, onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('cancel-upload-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) it('should allow cancellation during package ready-to-install', async () => { const onClose = vi.fn() const defaultProps = { file: createMockFile(), onClose, onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-package-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-package')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('package-close-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) it('should allow cancellation during bundle ready-to-install', async () => { const onClose = vi.fn() const defaultProps = { file: createMockBundleFile(), onClose, onSuccess: vi.fn(), } render() fireEvent.click(screen.getByTestId('trigger-bundle-upload-btn')) await waitFor(() => { expect(screen.getByTestId('ready-to-install-bundle')).toBeInTheDocument() }) fireEvent.click(screen.getByTestId('bundle-close-btn')) expect(onClose).toHaveBeenCalledTimes(1) }) }) })