launcher_controller_test.dart 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import 'package:cardash_launcher/data/models/app_entry.dart';
  2. import 'package:cardash_launcher/data/models/media_playback.dart';
  3. import 'package:cardash_launcher/data/models/slot_type.dart';
  4. import 'package:cardash_launcher/data/models/system_status.dart';
  5. import 'package:cardash_launcher/data/repositories/settings_repository.dart';
  6. import 'package:cardash_launcher/data/services/native_bridge.dart';
  7. import 'package:cardash_launcher/features/home/launcher_controller.dart';
  8. import 'package:flutter_test/flutter_test.dart';
  9. import 'package:shared_preferences/shared_preferences.dart';
  10. class FakeLauncherPlatform implements LauncherPlatform {
  11. FakeLauncherPlatform(this.apps);
  12. final List<AppEntry> apps;
  13. @override
  14. Future<List<AppEntry>> listApps() async => apps;
  15. @override
  16. Future<void> launchApp({
  17. required String packageName,
  18. required String activityName,
  19. }) async {}
  20. @override
  21. Future<bool> isDefaultHome() async => true;
  22. @override
  23. Future<bool> requestDefaultHome() async => true;
  24. @override
  25. Future<SystemStatus> getStatus() async => const SystemStatus();
  26. @override
  27. Future<VolumeInfo> getVolume() async => const VolumeInfo();
  28. @override
  29. Future<void> setVolume(int index) async {}
  30. @override
  31. Future<bool> isMusicPlaying() async => false;
  32. @override
  33. Future<bool> sendMediaKey(String action) async => true;
  34. @override
  35. Future<MediaPlayback> getMediaState() async => const MediaPlayback();
  36. @override
  37. Future<bool> openNotificationAccessSettings() async => true;
  38. @override
  39. Future<void> setKeepScreenOn(bool enabled) async {}
  40. @override
  41. Future<bool> openUrl(String url) async => true;
  42. }
  43. List<AppEntry> _apps(int count) {
  44. return List.generate(
  45. count,
  46. (index) => AppEntry(
  47. packageName: 'com.example.app$index',
  48. activityName: 'com.example.app$index.Main',
  49. label: 'App $index',
  50. ),
  51. );
  52. }
  53. Future<LauncherController> _controller({
  54. int appCount = 20,
  55. FakeLauncherPlatform? platform,
  56. }) async {
  57. SharedPreferences.setMockInitialValues({});
  58. final prefs = await SharedPreferences.getInstance();
  59. final controller = LauncherController(
  60. platform ?? FakeLauncherPlatform(_apps(appCount)),
  61. SettingsRepository(prefs),
  62. );
  63. controller.initialize();
  64. return controller;
  65. }
  66. void main() {
  67. TestWidgetsFlutterBinding.ensureInitialized();
  68. test('parses the native system-app flag', () {
  69. final system = AppEntry.fromMap({
  70. 'package': 'com.example.system',
  71. 'activity': 'com.example.system.Main',
  72. 'label': 'System',
  73. 'system': true,
  74. });
  75. final user = AppEntry.fromMap({
  76. 'package': 'com.example.user',
  77. 'activity': 'com.example.user.Main',
  78. 'label': 'User',
  79. 'system': false,
  80. });
  81. expect(system.isSystemApp, isTrue);
  82. expect(user.isSystemApp, isFalse);
  83. });
  84. test('home buttons default to enabled and count the fixed buttons', () async {
  85. final controller = await _controller();
  86. addTearDown(controller.dispose);
  87. expect(controller.isSlotEnabled(SlotType.navigation), isTrue);
  88. expect(controller.isSlotEnabled(SlotType.music), isTrue);
  89. expect(controller.isSlotEnabled(SlotType.radio), isTrue);
  90. expect(controller.homeButtonCount, 4);
  91. expect(controller.pinnedApps, isEmpty);
  92. });
  93. test('disabling a slot hides it and keeps the assignment', () async {
  94. final controller = await _controller();
  95. addTearDown(controller.dispose);
  96. final app = _apps(1).first;
  97. await controller.assign(SlotType.navigation, app);
  98. expect(await controller.setSlotEnabled(SlotType.navigation, false), isNull);
  99. expect(controller.isSlotEnabled(SlotType.navigation), isFalse);
  100. expect(controller.homeButtonCount, 3);
  101. expect(controller.assignmentFor(SlotType.navigation)?.label, 'App 0');
  102. expect(await controller.setSlotEnabled(SlotType.navigation, true), isNull);
  103. expect(controller.homeButtonCount, 4);
  104. });
  105. test('pinned apps can be added once and removed again', () async {
  106. final controller = await _controller();
  107. addTearDown(controller.dispose);
  108. final apps = _apps(3);
  109. expect(await controller.addPinnedApp(apps[0]), isNull);
  110. expect(controller.pinnedApps.length, 1);
  111. expect(controller.homeButtonCount, 5);
  112. expect(await controller.addPinnedApp(apps[0]), contains('already'));
  113. await controller.removePinnedApp(apps[0].toAssignment());
  114. expect(controller.pinnedApps, isEmpty);
  115. expect(controller.homeButtonCount, 4);
  116. });
  117. test('the home screen stops at 16 buttons', () async {
  118. final controller = await _controller();
  119. addTearDown(controller.dispose);
  120. final apps = _apps(20);
  121. for (var index = 0; index < 12; index++) {
  122. expect(await controller.addPinnedApp(apps[index]), isNull);
  123. }
  124. expect(controller.homeButtonCount, 16);
  125. expect(await controller.addPinnedApp(apps[12]), contains('full'));
  126. expect(controller.pinnedApps.length, 12);
  127. await controller.removePinnedApp(apps[0].toAssignment());
  128. expect(controller.homeButtonCount, 15);
  129. expect(await controller.addPinnedApp(apps[12]), isNull);
  130. expect(controller.homeButtonCount, 16);
  131. });
  132. test('pinned labels and custom icons persist', () async {
  133. SharedPreferences.setMockInitialValues({});
  134. final prefs = await SharedPreferences.getInstance();
  135. final apps = _apps(3);
  136. final platform = FakeLauncherPlatform(apps);
  137. final first = LauncherController(platform, SettingsRepository(prefs));
  138. first.initialize();
  139. addTearDown(first.dispose);
  140. expect(await first.addPinnedApp(apps[0]), isNull);
  141. final pinned = first.pinnedApps.first;
  142. await first.renamePinnedApp(pinned, 'My shortcut');
  143. await first.setPinnedIcon(pinned, 'assets/icons/star.svg');
  144. final second = LauncherController(platform, SettingsRepository(prefs));
  145. second.initialize();
  146. addTearDown(second.dispose);
  147. expect(second.pinnedApps.length, 1);
  148. expect(second.pinnedApps.first.label, 'My shortcut');
  149. expect(second.pinnedApps.first.iconAsset, 'assets/icons/star.svg');
  150. });
  151. test('pinned apps and slot visibility survive a new controller', () async {
  152. SharedPreferences.setMockInitialValues({});
  153. final prefs = await SharedPreferences.getInstance();
  154. final platform = FakeLauncherPlatform(_apps(3));
  155. final settings = SettingsRepository(prefs);
  156. final first = LauncherController(platform, settings);
  157. first.initialize();
  158. addTearDown(first.dispose);
  159. await first.setSlotEnabled(SlotType.radio, false);
  160. expect(await first.addPinnedApp(_apps(1).first), isNull);
  161. final second = LauncherController(platform, SettingsRepository(prefs));
  162. second.initialize();
  163. addTearDown(second.dispose);
  164. expect(second.isSlotEnabled(SlotType.radio), isFalse);
  165. expect(second.pinnedApps.length, 1);
  166. expect(second.pinnedApps.first.label, 'App 0');
  167. expect(second.homeButtonCount, 4);
  168. });
  169. }