# Installation Flow Testing Environment This comprehensive testing environment allows you to test all installation flows end-to-end with mocked `uv sync`, `uvicorn`, and Electron APIs. It simulates different system states and provides utilities to change the environment during tests. ## Overview The testing environment consists of three main components: 1. **Electron API Mocks** (`test/mocks/electronMocks.ts`) - Mock Electron's preload APIs 2. **Environment State Mocks** (`test/mocks/environmentMocks.ts`) - Mock filesystem, processes, and system state 3. **Test Scenarios** - Predefined scenarios for different installation flows ## Quick Start ```typescript import { setupElectronMocks, TestScenarios } from '../mocks/electronMocks' import { setupMockEnvironment } from '../mocks/environmentMocks' describe('My Installation Test', () => { let electronAPI: MockedElectronAPI let mockEnv: ReturnType beforeEach(() => { // Set up mocks const { electronAPI: api } = setupElectronMocks() electronAPI = api mockEnv = setupMockEnvironment() }) it('should handle version update', async () => { // Apply scenario TestScenarios.versionUpdate(electronAPI) // Your test code here }) }) ``` ## Electron API Mocks ### Available Mock Methods - `checkAndInstallDepsOnUpdate()` - Simulates dependency installation - `getInstallationStatus()` - Returns current installation status - `exportLog()` - Simulates log export functionality - Event listeners for installation events ### Simulation Functions ```typescript // Simulate installation events electronAPI.simulateInstallationStart() electronAPI.simulateInstallationLog('stdout', 'Installing packages...') electronAPI.simulateInstallationComplete(true) // or false for failure // Simulate system changes electronAPI.simulateVersionChange('2.0.0') electronAPI.simulateVenvRemoval() electronAPI.simulateUvicornStartup() ``` ### Mock State Control ```typescript // Control the mock state directly electronAPI.mockState.venvExists = false electronAPI.mockState.isInstalling = true electronAPI.mockState.toolInstalled = false ``` ## Environment State Mocks ### Filesystem Mock Controls file system operations: ```typescript // Control file existence mockEnv.mockState.filesystem.venvExists = false mockEnv.mockState.filesystem.versionFileExists = true mockEnv.mockState.filesystem.installedLockExists = false // Control file contents mockEnv.mockState.filesystem.versionFileContent = '0.9.0' ``` ### Process Mock Controls process spawning and execution: ```typescript // Control tool availability mockEnv.mockState.processes.uvAvailable = false mockEnv.mockState.processes.bunAvailable = true mockEnv.mockState.processes.uvicornRunning = false // Control network connectivity mockEnv.mockState.network.canConnectToDefault = false mockEnv.mockState.network.canConnectToMirror = true ``` ## Predefined Test Scenarios ### Electron API Scenarios Use `TestScenarios` from `electronMocks.ts`: ```typescript // Fresh installation - no .venv, no version file TestScenarios.freshInstall(electronAPI) // Version update - version file exists but version changed TestScenarios.versionUpdate(electronAPI) // .venv removed - version file exists but .venv is missing TestScenarios.venvRemoved(electronAPI) // Installation in progress - when user opens app during installation TestScenarios.installationInProgress(electronAPI) // Installation error scenario TestScenarios.installationError(electronAPI) // Uvicorn startup with dependency installation TestScenarios.uvicornDepsInstall(electronAPI) // All good - no installation needed TestScenarios.allGood(electronAPI) ``` ### Environment Scenarios Use `mockEnv.scenarios` from `environmentMocks.ts`: ```typescript // Fresh installation mockEnv.scenarios.freshInstall() // Version update mockEnv.scenarios.versionUpdate('0.9.0', '1.0.0') // .venv removed mockEnv.scenarios.venvRemoved() // Network issues mockEnv.scenarios.networkIssues() // Complete failure mockEnv.scenarios.completeFailure() // Uvicorn startup installation mockEnv.scenarios.uvicornStartupInstall() // Installation in progress mockEnv.scenarios.installationInProgress() ``` ## Testing Different Installation States ### Installation Store States Test all possible states from `installationStore.ts`: - `'idle'` - Initial state - `'checking-permissions'` - Checking system permissions - `'showing-carousel'` - Showing onboarding carousel - `'installing'` - Installation in progress - `'error'` - Installation failed - `'completed'` - Installation successful ```typescript import { useInstallationStore } from '@/store/installationStore' it('should transition through all states', () => { const store = useInstallationStore.getState() expect(store.state).toBe('idle') store.startInstallation() expect(store.state).toBe('installing') store.setError('Installation failed') expect(store.state).toBe('error') store.retryInstallation() expect(store.state).toBe('installing') store.setSuccess() expect(store.state).toBe('completed') }) ``` ## Specific Test Cases ### 1. Testing .venv Removal ```typescript it('should handle .venv removal', async () => { // Simulate .venv being removed TestScenarios.venvRemoved(electronAPI) // or mockEnv.scenarios.venvRemoved() // Test your component/hook const result = await electronAPI.checkAndInstallDepsOnUpdate() expect(result.success).toBe(true) }) ``` ### 2. Testing Version File Changes ```typescript it('should handle version file changes', async () => { // Simulate version change TestScenarios.versionUpdate(electronAPI) // or mockEnv.scenarios.versionUpdate('0.9.0', '1.0.0') // Your test assertions }) ``` ### 3. Testing Uvicorn Startup Installation ```typescript it('should handle uvicorn starting with dependency installation', async () => { // Simulate uvicorn detecting missing dependencies TestScenarios.uvicornDepsInstall(electronAPI) // Trigger uvicorn startup electronAPI.simulateUvicornStartup() // Wait for installation events await waitFor(() => { expect(mockInstallationStore.startInstallation).toHaveBeenCalled() }) }) ``` ### 4. Testing UI Installation States ```typescript it('should show correct UI for each installation state', () => { const { result } = renderHook(() => useInstallationStore()) // Test idle state expect(result.current.state).toBe('idle') expect(result.current.isVisible).toBe(false) // Test installing state act(() => result.current.startInstallation()) expect(result.current.state).toBe('installing') expect(result.current.isVisible).toBe(true) // Test error state act(() => result.current.setError('Installation failed')) expect(result.current.state).toBe('error') expect(result.current.error).toBe('Installation failed') // Test completed state act(() => result.current.setSuccess()) expect(result.current.state).toBe('completed') expect(result.current.progress).toBe(100) }) ``` ## Advanced Testing Patterns ### Testing Event Sequences ```typescript it('should handle complete installation flow', async () => { const events: string[] = [] // Set up event tracking electronAPI.onInstallDependenciesStart(() => events.push('start')) electronAPI.onInstallDependenciesLog(() => events.push('log')) electronAPI.onInstallDependenciesComplete(() => events.push('complete')) // Trigger installation await electronAPI.checkAndInstallDepsOnUpdate() // Verify event sequence expect(events).toEqual(['start', 'log', 'log', 'complete']) }) ``` ### Testing Error Recovery ```typescript it('should recover from installation errors', async () => { // Set up error scenario TestScenarios.installationError(electronAPI) const store = useInstallationStore.getState() // Trigger installation await store.performInstallation() expect(store.state).toBe('error') // Simulate retry TestScenarios.allGood(electronAPI) // Fix the environment store.retryInstallation() await waitFor(() => { expect(store.state).toBe('completed') }) }) ``` ### Testing Concurrent Operations ```typescript it('should handle concurrent installation attempts', async () => { const store = useInstallationStore.getState() // Start multiple installations const promise1 = store.performInstallation() const promise2 = store.performInstallation() // Should handle gracefully const [result1, result2] = await Promise.all([promise1, promise2]) expect(store.state).toBe('completed') }) ``` ## Debugging Tests ### Logging Mock State ```typescript // Log current mock state console.log('Electron API State:', electronAPI.mockState) console.log('Environment State:', mockEnv.mockState) // Check what functions were called console.log('checkAndInstallDepsOnUpdate calls:', electronAPI.checkAndInstallDepsOnUpdate.mock.calls) ``` ### Waiting for Async Operations ```typescript import { waitForStateChange } from '../mocks/environmentMocks' // Wait for specific state changes await waitForStateChange( () => mockEnv.mockState.processes.uvSyncInProgress, true, 1000 // timeout ) ``` ## Running the Tests ```bash # Run all installation tests npm test test/unit/store/installationStore.test.ts npm test test/unit/hooks/useInstallationSetup.test.ts npm test test/unit/electron/install-deps.test.ts # Run with coverage npm test -- --coverage # Run in watch mode npm test -- --watch ``` ## Common Issues and Solutions ### 1. Mock Not Applied **Problem**: Mock functions not being called **Solution**: Ensure mocks are set up before importing modules ```typescript beforeEach(async () => { setupMocks() // Set up first const module = await import('./module') // Import after }) ``` ### 2. State Not Updating **Problem**: Mock state changes not reflected **Solution**: Use simulation functions instead of direct state mutation ```typescript // Don't do this electronAPI.mockState.isInstalling = true // Do this instead electronAPI.simulateInstallationStart() ``` ### 3. Async Operations Not Completing **Problem**: Tests timeout waiting for async operations **Solution**: Use proper wait functions and increase timeouts ```typescript await vi.waitFor(() => { expect(condition).toBe(true) }, { timeout: 2000 }) ``` ## Best Practices 1. **Reset State**: Always reset mock state between tests 2. **Use Scenarios**: Prefer predefined scenarios over manual state setup 3. **Test Edge Cases**: Include error conditions and edge cases 4. **Verify Events**: Check that the correct events are emitted 5. **Test Cleanup**: Verify that resources are properly cleaned up 6. **Integration Tests**: Test the complete flow, not just individual functions ## Example Test Files - `test/unit/store/installationStore.test.ts` - Store state management - `test/unit/hooks/useInstallationSetup.test.ts` - Hook behavior - `test/unit/electron/install-deps.test.ts` - Backend installation logic These test files demonstrate all the patterns and scenarios described in this README.