# 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 1. **Environment State Mocks** (`test/mocks/environmentMocks.ts`) - Mock filesystem, processes, and system state 1. **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 1. **Use Scenarios**: Prefer predefined scenarios over manual state setup 1. **Test Edge Cases**: Include error conditions and edge cases 1. **Verify Events**: Check that the correct events are emitted 1. **Test Cleanup**: Verify that resources are properly cleaned up 1. **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.