import 'package:test/test.dart'; import 'package:sqlite3/sqlite3.dart'; import 'package:xp_nix/src/monitors/productivity_monitor.dart'; import '../lib/src/testing/mock_idle_monitor.dart'; import '../lib/src/testing/mock_activity_detector.dart'; import '../lib/src/testing/mock_time_provider.dart'; import '../lib/src/testing/mock_desktop_enhancer.dart'; import '../lib/src/config/config_manager.dart'; void main() { group('Deep Idle Event Tests', () { late Database db; late ProductivityMonitor monitor; late MockIdleMonitor mockIdleMonitor; late MockActivityDetector mockActivityDetector; late MockTimeProvider mockTimeProvider; late MockDesktopEnhancer mockDesktopEnhancer; setUp(() async { // Create in-memory database for testing db = sqlite3.openInMemory(); // Reset and initialize ConfigManager with default config ConfigManager.resetInstance(); await ConfigManager.instance.initialize('/tmp/test_config_${DateTime.now().millisecondsSinceEpoch}.json'); // Create mock dependencies mockIdleMonitor = MockIdleMonitor(); mockActivityDetector = MockActivityDetector(); mockTimeProvider = MockTimeProvider(); mockDesktopEnhancer = MockDesktopEnhancer(); // Set up starting time (Monday 9 AM) mockTimeProvider.setTime(DateTime(2024, 1, 1, 9, 0)); // Create testable monitor with mocked dependencies monitor = ProductivityMonitor( db: db, idleMonitor: mockIdleMonitor, activityDetector: mockActivityDetector, timeProvider: mockTimeProvider, desktopEnhancer: mockDesktopEnhancer, ); }); tearDown(() async { monitor.stop(); // Add a small delay to allow async operations to complete await Future.delayed(Duration(milliseconds: 100)); try { db.dispose(); } catch (e) { // Database might already be closed, ignore the error } }); test('should end current activity and award XP when user goes deep idle', () async { // Start the monitor monitor.start(); await Future.delayed(Duration(milliseconds: 10)); print('\n๐Ÿงช Testing deep idle behavior...'); // Start a coding activity mockActivityDetector.simulateActivity('vscode', 'main.dart - TestProject'); await Future.delayed(Duration(milliseconds: 10)); // Work for 30 minutes mockTimeProvider.advanceTime(Duration(minutes: 30)); // Check that no activity has been saved yet (still in progress) var stats = monitor.getTodayStats(); var initialXP = stats['xp'] as int; print('๐Ÿ“Š Initial XP before deep idle: $initialXP'); // User goes deep idle - this should trigger ending the current activity print('๐Ÿ˜ด User goes deep idle...'); mockIdleMonitor.simulateDeepIdle(); await Future.delayed(Duration(milliseconds: 50)); // Allow event processing // Check that the activity was saved and XP was awarded stats = monitor.getTodayStats(); var finalXP = stats['xp'] as int; var focusTime = stats['focus_time'] as int; print('๐Ÿ“Š Final XP after deep idle: $finalXP'); print('๐Ÿ“Š Focus time: ${(focusTime / 60).toStringAsFixed(1)} minutes'); // Verify that XP was awarded for the 30-minute coding session expect(finalXP, greaterThan(initialXP), reason: 'Should have earned XP from the coding session when going deep idle'); // Expected: 30 minutes * 10 XP/min = 300 XP (base, before multipliers) expect(finalXP, greaterThan(200), reason: 'Should have earned substantial XP from 30 minutes of coding'); // Should have focus time from coding expect(focusTime, greaterThan(25 * 60), reason: 'Should have at least 25 minutes of focus time recorded'); print('โœ… Deep idle activity ending test passed!'); }); test('should not duplicate XP when going from light idle to deep idle', () async { // Start the monitor monitor.start(); await Future.delayed(Duration(milliseconds: 10)); print('\n๐Ÿงช Testing light idle to deep idle transition...'); // Start a coding activity mockActivityDetector.simulateActivity('vscode', 'feature.dart'); await Future.delayed(Duration(milliseconds: 10)); // Work for 20 minutes mockTimeProvider.advanceTime(Duration(minutes: 20)); // User goes light idle first print('๐Ÿ˜ User goes light idle...'); mockIdleMonitor.simulateLightIdle(); await Future.delayed(Duration(milliseconds: 50)); var stats = monitor.getTodayStats(); var xpAfterLightIdle = stats['xp'] as int; print('๐Ÿ“Š XP after light idle: $xpAfterLightIdle'); // Then user goes deep idle print('๐Ÿ˜ด User goes deep idle...'); mockIdleMonitor.simulateDeepIdle(); await Future.delayed(Duration(milliseconds: 50)); stats = monitor.getTodayStats(); var xpAfterDeepIdle = stats['xp'] as int; print('๐Ÿ“Š XP after deep idle: $xpAfterDeepIdle'); // XP should have been awarded when going deep idle, but not duplicated expect(xpAfterDeepIdle, greaterThan(0), reason: 'Should have earned XP from the coding session'); // The XP should be the same whether we went through light idle or not // (since the activity should only be saved once when going deep idle) expect(xpAfterDeepIdle, greaterThan(150), reason: 'Should have earned XP for 20 minutes of coding'); print('โœ… Light idle to deep idle transition test passed!'); }); test('should handle multiple activity sessions with deep idle interruptions', () async { // Start the monitor monitor.start(); await Future.delayed(Duration(milliseconds: 10)); print('\n๐Ÿงช Testing multiple sessions with deep idle interruptions...'); // First coding session mockActivityDetector.simulateActivity('vscode', 'session1.dart'); await Future.delayed(Duration(milliseconds: 10)); mockTimeProvider.advanceTime(Duration(minutes: 25)); // Go deep idle (should end first session) mockIdleMonitor.simulateDeepIdle(); await Future.delayed(Duration(milliseconds: 50)); var stats = monitor.getTodayStats(); var xpAfterFirstSession = stats['xp'] as int; print('๐Ÿ“Š XP after first session: $xpAfterFirstSession'); // User becomes active again mockIdleMonitor.simulateActive(); await Future.delayed(Duration(milliseconds: 10)); // Second coding session mockActivityDetector.simulateActivity('vscode', 'session2.dart'); await Future.delayed(Duration(milliseconds: 10)); mockTimeProvider.advanceTime(Duration(minutes: 35)); // Go deep idle again (should end second session) mockIdleMonitor.simulateDeepIdle(); await Future.delayed(Duration(milliseconds: 50)); stats = monitor.getTodayStats(); var finalXP = stats['xp'] as int; var totalFocusTime = stats['focus_time'] as int; print('๐Ÿ“Š Final XP after both sessions: $finalXP'); print('๐Ÿ“Š Total focus time: ${(totalFocusTime / 60).toStringAsFixed(1)} minutes'); // Should have XP from both sessions expect(finalXP, greaterThan(xpAfterFirstSession), reason: 'Should have earned additional XP from second session'); // Should have substantial XP from 60 minutes total (25 + 35) expect(finalXP, greaterThan(400), reason: 'Should have earned substantial XP from both coding sessions'); // Should have focus time from both sessions (at least 55 minutes) expect(totalFocusTime, greaterThan(55 * 60), reason: 'Should have focus time from both sessions'); print('โœ… Multiple sessions with deep idle test passed!'); }); }); }