import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest' import { act, renderHook } from '@testing-library/react' import { useInstallationStore, type InstallationState } from '../../../src/store/installationStore' import { setupElectronMocks, TestScenarios, type MockedElectronAPI } from '../../mocks/electronMocks' // Mock the authStore import since it's imported dynamically vi.mock('../../../src/store/authStore', () => ({ useAuthStore: { getState: () => ({ setInitState: vi.fn() }) } })) describe('Installation Store', () => { let electronAPI: MockedElectronAPI let mockSetInitState: ReturnType beforeEach(async () => { // Set up electron mocks const mocks = setupElectronMocks() electronAPI = mocks.electronAPI // Mock the authStore const { useAuthStore } = await import('../../../src/store/authStore') mockSetInitState = vi.fn() useAuthStore.getState = vi.fn().mockReturnValue({ setInitState: mockSetInitState }) // Reset the store to initial state useInstallationStore.getState().reset() }) afterEach(() => { vi.clearAllMocks() electronAPI.reset() }) describe('Initial State', () => { it('should have correct initial state', () => { const { result } = renderHook(() => useInstallationStore()) expect(result.current.state).toBe('idle') expect(result.current.progress).toBe(20) expect(result.current.logs).toEqual([]) expect(result.current.error).toBeUndefined() expect(result.current.isVisible).toBe(false) }) }) describe('State Transitions', () => { it('should transition from idle to installing when startInstallation is called', () => { const { result } = renderHook(() => useInstallationStore()) act(() => { result.current.startInstallation() }) expect(result.current.state).toBe('installing') expect(result.current.progress).toBe(20) expect(result.current.logs).toEqual([]) expect(result.current.error).toBeUndefined() expect(result.current.isVisible).toBe(true) }) it('should transition to completed when setSuccess is called', () => { const { result } = renderHook(() => useInstallationStore()) act(() => { result.current.startInstallation() }) act(() => { result.current.setSuccess() }) expect(result.current.state).toBe('completed') expect(result.current.progress).toBe(100) }) it('should transition to error when setError is called', () => { const { result } = renderHook(() => useInstallationStore()) const errorMessage = 'Installation failed' act(() => { result.current.startInstallation() }) act(() => { result.current.setError(errorMessage) }) expect(result.current.state).toBe('error') expect(result.current.error).toBe(errorMessage) expect(result.current.logs).toHaveLength(1) expect(result.current.logs[0].type).toBe('stderr') expect(result.current.logs[0].data).toBe(errorMessage) }) it('should reset to installing state when retryInstallation is called', () => { const { result } = renderHook(() => useInstallationStore()) // First, set error state act(() => { result.current.startInstallation() }) act(() => { result.current.setError('Some error') }) expect(result.current.state).toBe('error') // Then retry act(() => { result.current.retryInstallation() }) expect(result.current.state).toBe('installing') expect(result.current.logs).toEqual([]) expect(result.current.error).toBeUndefined() expect(result.current.isVisible).toBe(true) }) }) describe('Log Management', () => { it('should add logs and update progress', () => { const { result } = renderHook(() => useInstallationStore()) act(() => { result.current.startInstallation() }) const initialProgress = result.current.progress act(() => { result.current.addLog({ type: 'stdout', data: 'Installing package...', timestamp: new Date() }) }) expect(result.current.logs).toHaveLength(1) expect(result.current.logs[0].type).toBe('stdout') expect(result.current.logs[0].data).toBe('Installing package...') expect(result.current.progress).toBe(initialProgress + 5) }) it('should not exceed 90% progress when adding logs', () => { const { result } = renderHook(() => useInstallationStore()) act(() => { result.current.startInstallation() }) // Add many logs to test progress cap act(() => { for (let i = 0; i < 20; i++) { result.current.addLog({ type: 'stdout', data: `Log entry ${i}`, timestamp: new Date() }) } }) expect(result.current.progress).toBe(90) expect(result.current.logs).toHaveLength(20) }) }) describe('Installation Flow Integration', () => { it('should handle successful installation flow', async () => { TestScenarios.versionUpdate(electronAPI) const { result } = renderHook(() => useInstallationStore()) // Start installation await act(async () => { await result.current.performInstallation() }) // Wait for the mocked installation to complete await vi.waitFor(() => { expect(result.current.state).toBe('completed') }, { timeout: 1000 }) expect(electronAPI.checkAndInstallDepsOnUpdate).toHaveBeenCalled() expect(mockSetInitState).toHaveBeenCalledWith('done') }) it('should handle installation failure', async () => { TestScenarios.installationError(electronAPI) const { result } = renderHook(() => useInstallationStore()) await act(async () => { await result.current.performInstallation() }) // Wait for the mocked installation to fail await vi.waitFor(() => { expect(result.current.state).toBe('error') }, { timeout: 1000 }) expect(result.current.error).toBe('Installation failed') }) it('should handle fresh installation scenario', async () => { TestScenarios.freshInstall(electronAPI) const { result } = renderHook(() => useInstallationStore()) await act(async () => { await result.current.performInstallation() }) await vi.waitFor(() => { expect(result.current.state).toBe('completed') }, { timeout: 1000 }) expect(electronAPI.checkAndInstallDepsOnUpdate).toHaveBeenCalled() }) }) describe('Log Export', () => { it('should export logs successfully', async () => { const { result } = renderHook(() => useInstallationStore()) // Mock window.location.href const originalLocation = window.location Object.defineProperty(window, 'location', { value: { href: '' }, writable: true }) // Mock alert const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => {}) await act(async () => { await result.current.exportLog() }) expect(electronAPI.exportLog).toHaveBeenCalled() expect(alertSpy).toHaveBeenCalledWith('Log saved: /mock/path/to/log.txt') expect(window.location.href).toBe('https://github.com/eigent-ai/eigent/issues/new/choose') // Restore Object.defineProperty(window, 'location', { value: originalLocation, writable: true }) alertSpy.mockRestore() }) it('should handle export failure', async () => { electronAPI.exportLog.mockResolvedValue({ success: false, error: 'Export failed' }) const { result } = renderHook(() => useInstallationStore()) const alertSpy = vi.spyOn(window, 'alert').mockImplementation(() => {}) await act(async () => { await result.current.exportLog() }) expect(alertSpy).toHaveBeenCalledWith('Export cancelled: Export failed') alertSpy.mockRestore() }) }) describe('Computed Selectors', () => { it('useLatestLog should return the most recent log', () => { const { result: storeResult } = renderHook(() => useInstallationStore()) const { result: latestLogResult } = renderHook(() => useInstallationStore((state: any) => state.logs[state.logs.length - 1] )) expect(latestLogResult.current).toBeUndefined() act(() => { storeResult.current.startInstallation() storeResult.current.addLog({ type: 'stdout', data: 'First log', timestamp: new Date() }) storeResult.current.addLog({ type: 'stderr', data: 'Latest log', timestamp: new Date() }) }) expect(latestLogResult.current.data).toBe('Latest log') expect(latestLogResult.current.type).toBe('stderr') }) it('useInstallationStatus should return correct status', () => { const { result: storeResult } = renderHook(() => useInstallationStore()) const { result: statusResult } = renderHook(() => { const state = useInstallationStore((state: any) => state.state) const isVisible = useInstallationStore((state: any) => state.isVisible) return { isInstalling: state === 'installing', installationState: state, shouldShowInstallScreen: isVisible && state !== 'completed', isInstallationComplete: state === 'completed', canRetry: state === 'error', } }) // Initial state expect(statusResult.current.isInstalling).toBe(false) expect(statusResult.current.installationState).toBe('idle') expect(statusResult.current.shouldShowInstallScreen).toBe(false) expect(statusResult.current.isInstallationComplete).toBe(false) expect(statusResult.current.canRetry).toBe(false) // Installing state act(() => { storeResult.current.startInstallation() }) expect(statusResult.current.isInstalling).toBe(true) expect(statusResult.current.shouldShowInstallScreen).toBe(true) expect(statusResult.current.canRetry).toBe(false) // Error state act(() => { storeResult.current.setError('Some error') }) expect(statusResult.current.isInstalling).toBe(false) expect(statusResult.current.canRetry).toBe(true) // Completed state act(() => { storeResult.current.setSuccess() }) expect(statusResult.current.isInstallationComplete).toBe(true) expect(statusResult.current.shouldShowInstallScreen).toBe(false) }) }) describe('Edge Cases', () => { it('should handle multiple rapid state changes', () => { const { result } = renderHook(() => useInstallationStore()) act(() => { result.current.startInstallation() result.current.setError('Error 1') result.current.retryInstallation() result.current.setSuccess() }) expect(result.current.state).toBe('completed') expect(result.current.progress).toBe(100) }) it('should handle visibility changes correctly', () => { const { result } = renderHook(() => useInstallationStore()) expect(result.current.isVisible).toBe(false) act(() => { result.current.setVisible(true) }) expect(result.current.isVisible).toBe(true) act(() => { result.current.completeSetup() }) expect(result.current.state).toBe('completed') expect(result.current.isVisible).toBe(false) }) it('should handle manual progress updates', () => { const { result } = renderHook(() => useInstallationStore()) act(() => { result.current.updateProgress(75) }) expect(result.current.progress).toBe(75) }) }) describe('Installation State Sequence', () => { it('should follow correct state sequence for normal installation', async () => { const { result } = renderHook(() => useInstallationStore()) const states: InstallationState[] = [] // Subscribe to state changes useInstallationStore.subscribe((state: any) => { states.push(state.state) }) await act(async () => { await result.current.performInstallation() }) await vi.waitFor(() => { expect(result.current.state).toBe('completed') }, { timeout: 1000 }) // Should have progressed through: idle -> installing -> completed expect(states).toContain('installing') expect(states).toContain('completed') }) }) })