import 'package:cardash_launcher/data/models/app_entry.dart'; import 'package:cardash_launcher/data/models/media_playback.dart'; import 'package:cardash_launcher/data/models/slot_type.dart'; import 'package:cardash_launcher/data/models/system_status.dart'; import 'package:cardash_launcher/data/repositories/settings_repository.dart'; import 'package:cardash_launcher/data/services/native_bridge.dart'; import 'package:cardash_launcher/features/home/launcher_controller.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:shared_preferences/shared_preferences.dart'; class FakeLauncherPlatform implements LauncherPlatform { FakeLauncherPlatform(this.apps); final List apps; @override Future> listApps() async => apps; @override Future launchApp({ required String packageName, required String activityName, }) async {} @override Future isDefaultHome() async => true; @override Future requestDefaultHome() async => true; @override Future getStatus() async => const SystemStatus(); @override Future getVolume() async => const VolumeInfo(); @override Future setVolume(int index) async {} @override Future isMusicPlaying() async => false; @override Future sendMediaKey(String action) async => true; @override Future getMediaState() async => const MediaPlayback(); @override Future openNotificationAccessSettings() async => true; @override Future setKeepScreenOn(bool enabled) async {} } List _apps(int count) { return List.generate( count, (index) => AppEntry( packageName: 'com.example.app$index', activityName: 'com.example.app$index.Main', label: 'App $index', ), ); } Future _controller({ int appCount = 20, FakeLauncherPlatform? platform, }) async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final controller = LauncherController( platform ?? FakeLauncherPlatform(_apps(appCount)), SettingsRepository(prefs), ); controller.initialize(); return controller; } void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('parses the native system-app flag', () { final system = AppEntry.fromMap({ 'package': 'com.example.system', 'activity': 'com.example.system.Main', 'label': 'System', 'system': true, }); final user = AppEntry.fromMap({ 'package': 'com.example.user', 'activity': 'com.example.user.Main', 'label': 'User', 'system': false, }); expect(system.isSystemApp, isTrue); expect(user.isSystemApp, isFalse); }); test('home buttons default to enabled and count the fixed buttons', () async { final controller = await _controller(); addTearDown(controller.dispose); expect(controller.isSlotEnabled(SlotType.navigation), isTrue); expect(controller.isSlotEnabled(SlotType.music), isTrue); expect(controller.isSlotEnabled(SlotType.radio), isTrue); expect(controller.homeButtonCount, 4); expect(controller.pinnedApps, isEmpty); }); test('disabling a slot hides it and keeps the assignment', () async { final controller = await _controller(); addTearDown(controller.dispose); final app = _apps(1).first; await controller.assign(SlotType.navigation, app); expect(await controller.setSlotEnabled(SlotType.navigation, false), isNull); expect(controller.isSlotEnabled(SlotType.navigation), isFalse); expect(controller.homeButtonCount, 3); expect(controller.assignmentFor(SlotType.navigation)?.label, 'App 0'); expect(await controller.setSlotEnabled(SlotType.navigation, true), isNull); expect(controller.homeButtonCount, 4); }); test('pinned apps can be added once and removed again', () async { final controller = await _controller(); addTearDown(controller.dispose); final apps = _apps(3); expect(await controller.addPinnedApp(apps[0]), isNull); expect(controller.pinnedApps.length, 1); expect(controller.homeButtonCount, 5); expect(await controller.addPinnedApp(apps[0]), contains('already')); await controller.removePinnedApp(apps[0].toAssignment()); expect(controller.pinnedApps, isEmpty); expect(controller.homeButtonCount, 4); }); test('the home screen stops at 16 buttons', () async { final controller = await _controller(); addTearDown(controller.dispose); final apps = _apps(20); for (var index = 0; index < 12; index++) { expect(await controller.addPinnedApp(apps[index]), isNull); } expect(controller.homeButtonCount, 16); expect(await controller.addPinnedApp(apps[12]), contains('full')); expect(controller.pinnedApps.length, 12); await controller.removePinnedApp(apps[0].toAssignment()); expect(controller.homeButtonCount, 15); expect(await controller.addPinnedApp(apps[12]), isNull); expect(controller.homeButtonCount, 16); }); test('pinned labels and custom icons persist', () async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final apps = _apps(3); final platform = FakeLauncherPlatform(apps); final first = LauncherController(platform, SettingsRepository(prefs)); first.initialize(); addTearDown(first.dispose); expect(await first.addPinnedApp(apps[0]), isNull); final pinned = first.pinnedApps.first; await first.renamePinnedApp(pinned, 'My shortcut'); await first.setPinnedIcon(pinned, 'assets/icons/star.svg'); final second = LauncherController(platform, SettingsRepository(prefs)); second.initialize(); addTearDown(second.dispose); expect(second.pinnedApps.length, 1); expect(second.pinnedApps.first.label, 'My shortcut'); expect(second.pinnedApps.first.iconAsset, 'assets/icons/star.svg'); }); test('pinned apps and slot visibility survive a new controller', () async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final platform = FakeLauncherPlatform(_apps(3)); final settings = SettingsRepository(prefs); final first = LauncherController(platform, settings); first.initialize(); addTearDown(first.dispose); await first.setSlotEnabled(SlotType.radio, false); expect(await first.addPinnedApp(_apps(1).first), isNull); final second = LauncherController(platform, SettingsRepository(prefs)); second.initialize(); addTearDown(second.dispose); expect(second.isSlotEnabled(SlotType.radio), isFalse); expect(second.pinnedApps.length, 1); expect(second.pinnedApps.first.label, 'App 0'); expect(second.homeButtonCount, 4); }); }