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.
The testing environment consists of three main components:
- Electron API Mocks (
test/mocks/electronMocks.ts) - Mock Electron's preload APIs - Environment State Mocks (
test/mocks/environmentMocks.ts) - Mock filesystem, processes, and system state - Test Scenarios - Predefined scenarios for different installation flows
import { setupElectronMocks, TestScenarios } from '../mocks/electronMocks';
import { setupMockEnvironment } from '../mocks/environmentMocks';
describe('My Installation Test', () => {
let electronAPI: MockedElectronAPI;
let mockEnv: ReturnType<typeof setupMockEnvironment>;
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
});
});checkAndInstallDepsOnUpdate()- Simulates dependency installationgetInstallationStatus()- Returns current installation statusexportLog()- Simulates log export functionality- Event listeners for installation events
// 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();// Control the mock state directly
electronAPI.mockState.venvExists = false;
electronAPI.mockState.isInstalling = true;
electronAPI.mockState.toolInstalled = false;Controls file system operations:
// 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';Controls process spawning and execution:
// 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;Use TestScenarios from electronMocks.ts:
// 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);Use mockEnv.scenarios from environmentMocks.ts:
// 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();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
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');
});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);
});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
});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();
});
});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);
});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']);
});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');
});
});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');
});// 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
);import { waitForStateChange } from '../mocks/environmentMocks';
// Wait for specific state changes
await waitForStateChange(
() => mockEnv.mockState.processes.uvSyncInProgress,
true,
1000 // timeout
);# 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 -- --watchProblem: Mock functions not being called Solution: Ensure mocks are set up before importing modules
beforeEach(async () => {
setupMocks(); // Set up first
const module = await import('./module'); // Import after
});Problem: Mock state changes not reflected Solution: Use simulation functions instead of direct state mutation
// Don't do this
electronAPI.mockState.isInstalling = true;
// Do this instead
electronAPI.simulateInstallationStart();Problem: Tests timeout waiting for async operations Solution: Use proper wait functions and increase timeouts
await vi.waitFor(
() => {
expect(condition).toBe(true);
},
{ timeout: 2000 }
);- Reset State: Always reset mock state between tests
- Use Scenarios: Prefer predefined scenarios over manual state setup
- Test Edge Cases: Include error conditions and edge cases
- Verify Events: Check that the correct events are emitted
- Test Cleanup: Verify that resources are properly cleaned up
- Integration Tests: Test the complete flow, not just individual functions
test/unit/store/installationStore.test.ts- Store state managementtest/unit/hooks/useInstallationSetup.test.ts- Hook behaviortest/unit/electron/install-deps.test.ts- Backend installation logic
These test files demonstrate all the patterns and scenarios described in this README.