Skip to content

Commit ff6bb71

Browse files
committed
tests: increase test coverage
1 parent cd91b42 commit ff6bb71

19 files changed

Lines changed: 1615 additions & 4 deletions

‎lib/app/cubit/app_cubit.dart‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,14 @@ Otherwise, [consider signing in using X11 instead](https://docs.fedoraproject.or
133133
// version data for Windows so they don't get an "update available" message.
134134
if (Platform.isWindows) return;
135135

136-
final versionInfo = await _updateService.getVersionInfo();
136+
final VersionInfo versionInfo;
137+
try {
138+
versionInfo = await _updateService.getVersionInfo();
139+
} on Exception catch (e) {
140+
log.e('Error fetching version info: $e');
141+
return;
142+
}
143+
137144
emit(
138145
state.copyWith(
139146
runningVersion: versionInfo.currentVersion,

‎lib/updates/update_service.dart‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:convert';
22

3+
import 'package:flutter/foundation.dart';
34
import 'package:http/http.dart' as http;
45
import 'package:http/http.dart';
56
import 'package:package_info_plus/package_info_plus.dart';
@@ -64,7 +65,7 @@ class UpdateService {
6465
final data = List<Map>.from(json);
6566
final tag = data.firstWhere((element) => element['prerelease'] == false);
6667
final tagName = tag['tag_name'] as String;
67-
final version = _parseVersionTag(tagName);
68+
final version = parseVersionTag(tagName);
6869
return Version.parse(version);
6970
} else {
7071
log.w(
@@ -81,7 +82,8 @@ class UpdateService {
8182
/// Examples:
8283
/// `v1.2.3` becomes `1.2.3`.
8384
/// `v1.2.3-beta` becomes `1.2.3`.
84-
String _parseVersionTag(String tag) {
85+
@visibleForTesting
86+
String parseVersionTag(String tag) {
8587
final version = tag.split('v').last.split('-').first;
8688
return version;
8789
}

‎pubspec.lock‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,7 @@ packages:
793793
source: hosted
794794
version: "2.2.1"
795795
path_provider_platform_interface:
796-
dependency: transitive
796+
dependency: "direct dev"
797797
description:
798798
name: path_provider_platform_interface
799799
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"

‎pubspec.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ dev_dependencies:
7777
json_serializable: ^6.7.1
7878
mockito: ^5.4.2
7979
msix: ^3.16.7
80+
path_provider_platform_interface: ^2.1.2
8081
test: ^1.22.0
8182

8283
flutter:

‎test/app/cubit/app_cubit_test.dart‎

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:helpers/helpers.dart';
24
import 'package:mockito/annotations.dart';
35
import 'package:mockito/mockito.dart';
@@ -61,4 +63,175 @@ void main() {
6163
expect(state.firstRun, true);
6264
});
6365
});
66+
67+
group('AppCubit - first run:', () {
68+
test(
69+
'when storage returns null for firstRun, state has firstRun: true after init',
70+
() async {
71+
when(
72+
mockStorageRepo.getValue('firstRun'),
73+
).thenAnswer((_) async => null);
74+
75+
final testCubit = AppCubit(
76+
mockAppWindow,
77+
mockNativePlatform,
78+
mockReleaseNotesService,
79+
mockStorageRepo,
80+
mockSystemTrayManager,
81+
mockUpdateService,
82+
);
83+
84+
// Allow async _init() to complete.
85+
await Future<void>.delayed(Duration.zero);
86+
87+
expect(testCubit.state.firstRun, true);
88+
},
89+
);
90+
91+
test(
92+
'when storage returns false for firstRun, state has firstRun: false after init',
93+
() async {
94+
when(
95+
mockStorageRepo.getValue('firstRun'),
96+
).thenAnswer((_) async => false);
97+
98+
final testCubit = AppCubit(
99+
mockAppWindow,
100+
mockNativePlatform,
101+
mockReleaseNotesService,
102+
mockStorageRepo,
103+
mockSystemTrayManager,
104+
mockUpdateService,
105+
);
106+
107+
await Future<void>.delayed(Duration.zero);
108+
109+
expect(testCubit.state.firstRun, false);
110+
},
111+
);
112+
113+
test('userAcceptedDisclaimer() saves firstRun: false to storage', () async {
114+
// Allow async _init() (which may also call saveValue) to complete first.
115+
await Future<void>.delayed(Duration.zero);
116+
reset(mockStorageRepo);
117+
118+
await cubit.userAcceptedDisclaimer();
119+
120+
verify(
121+
mockStorageRepo.saveValue(key: 'firstRun', value: false),
122+
).called(1);
123+
});
124+
125+
test('userAcceptedDisclaimer() emits state with firstRun: false', () async {
126+
await cubit.userAcceptedDisclaimer();
127+
128+
expect(state.firstRun, false);
129+
});
130+
});
131+
132+
group('AppCubit - version fetching:', () {
133+
test(
134+
'_fetchVersionData() populates state with version information',
135+
() async {
136+
const populatedVersionInfo = VersionInfo(
137+
currentVersion: '2.0.0',
138+
latestVersion: '2.1.0',
139+
updateAvailable: true,
140+
);
141+
when(
142+
mockUpdateService.getVersionInfo(),
143+
).thenAnswer((_) async => populatedVersionInfo);
144+
145+
final testCubit = AppCubit(
146+
mockAppWindow,
147+
mockNativePlatform,
148+
mockReleaseNotesService,
149+
mockStorageRepo,
150+
mockSystemTrayManager,
151+
mockUpdateService,
152+
);
153+
154+
// Wait for _init() async operations to complete.
155+
await Future<void>.delayed(Duration.zero);
156+
157+
expect(testCubit.state.runningVersion, '2.0.0');
158+
expect(testCubit.state.updateVersion, '2.1.0');
159+
expect(testCubit.state.updateAvailable, true);
160+
161+
await testCubit.close();
162+
},
163+
);
164+
165+
test(
166+
'when getVersionInfo() throws, state is not corrupted',
167+
() async {
168+
when(
169+
mockUpdateService.getVersionInfo(),
170+
).thenThrow(Exception('Network error'));
171+
172+
final testCubit = AppCubit(
173+
mockAppWindow,
174+
mockNativePlatform,
175+
mockReleaseNotesService,
176+
mockStorageRepo,
177+
mockSystemTrayManager,
178+
mockUpdateService,
179+
);
180+
181+
// Wait for _init() async operations to complete.
182+
await Future<void>.delayed(Duration.zero);
183+
184+
// State should remain at initial values when fetching throws.
185+
expect(testCubit.state.runningVersion, '');
186+
expect(testCubit.state.updateAvailable, false);
187+
188+
await testCubit.close();
189+
},
190+
);
191+
});
192+
193+
group('AppCubit - system tray events:', () {
194+
late StreamController<SystemTrayEvent> trayEventController;
195+
late MockSystemTrayManager localSystemTrayManager;
196+
late AppCubit testCubit;
197+
198+
setUp(() async {
199+
localSystemTrayManager = MockSystemTrayManager();
200+
trayEventController = StreamController<SystemTrayEvent>();
201+
when(
202+
localSystemTrayManager.eventStream,
203+
).thenAnswer((_) => trayEventController.stream);
204+
205+
testCubit = AppCubit(
206+
mockAppWindow,
207+
mockNativePlatform,
208+
mockReleaseNotesService,
209+
mockStorageRepo,
210+
localSystemTrayManager,
211+
mockUpdateService,
212+
);
213+
214+
// Wait for _init() to complete so _listenToSystemTrayEvents() has subscribed.
215+
await Future<void>.delayed(Duration.zero);
216+
});
217+
218+
tearDown(() async {
219+
await trayEventController.close();
220+
await testCubit.close();
221+
});
222+
223+
test('SystemTrayEvent.windowShow calls appWindow.show()', () async {
224+
trayEventController.add(SystemTrayEvent.windowShow);
225+
await Future<void>.delayed(Duration.zero);
226+
227+
verify(mockAppWindow.show()).called(1);
228+
});
229+
230+
test('SystemTrayEvent.exit calls appWindow.close()', () async {
231+
trayEventController.add(SystemTrayEvent.exit);
232+
await Future<void>.delayed(Duration.zero);
233+
234+
verify(mockAppWindow.close()).called(1);
235+
});
236+
});
64237
}

‎test/apps_list/cubit/apps_list_cubit_test.dart‎

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,5 +452,99 @@ void main() {
452452
expect(mpvWindow2State.process.status, ProcessStatus.suspended);
453453
});
454454
});
455+
456+
group('showHiddenWindows:', () {
457+
test('fetchWindows passes showHidden: false by default', () async {
458+
await cubit.manualRefresh();
459+
verify(
460+
nativePlatform.windows(showHidden: false),
461+
).called(greaterThanOrEqualTo(1));
462+
});
463+
464+
test('fetchWindows passes showHidden: true when setting is enabled', () async {
465+
when(settingsCubit.state).thenReturn(
466+
SettingsState(
467+
appSpecificHotKeys: [],
468+
autoStart: false,
469+
autoRefresh: false,
470+
closeToTray: false,
471+
hotKey: HotKey(key: PhysicalKeyboardKey.again),
472+
minimizeWindows: true,
473+
pinSuspendedWindows: false,
474+
refreshInterval: 5,
475+
showHiddenWindows: true,
476+
startHiddenInTray: false,
477+
working: false,
478+
),
479+
);
480+
await cubit.manualRefresh();
481+
verify(
482+
nativePlatform.windows(showHidden: true),
483+
).called(greaterThanOrEqualTo(1));
484+
});
485+
});
486+
487+
group('favorites:', () {
488+
test('setFavorite(true) persists executable to storage', () async {
489+
when(
490+
nativePlatform.windows(showHidden: anyNamed('showHidden')),
491+
).thenAnswer((_) async => [msPaintWindow]);
492+
when(storage.getValue('favorites')).thenAnswer((_) async => <String>[]);
493+
await cubit.manualRefresh();
494+
await cubit.setFavorite(msPaintWindow, true);
495+
final captured = verify(
496+
storage.saveValue(
497+
key: 'favorites',
498+
value: captureAnyNamed('value'),
499+
),
500+
).captured;
501+
expect(captured.single, contains(msPaintProcess.executable));
502+
});
503+
504+
test('setFavorite(false) removes executable from storage', () async {
505+
when(
506+
nativePlatform.windows(showHidden: anyNamed('showHidden')),
507+
).thenAnswer((_) async => [msPaintWindow]);
508+
when(storage.getValue('favorites')).thenAnswer(
509+
(_) async => <String>[msPaintProcess.executable],
510+
);
511+
await cubit.manualRefresh();
512+
await cubit.setFavorite(msPaintWindow, false);
513+
final captured = verify(
514+
storage.saveValue(
515+
key: 'favorites',
516+
value: captureAnyNamed('value'),
517+
),
518+
).captured;
519+
expect(captured.single, isNot(contains(msPaintProcess.executable)));
520+
});
521+
522+
test('windows marked as favorite after refresh when stored', () async {
523+
when(
524+
nativePlatform.windows(showHidden: anyNamed('showHidden')),
525+
).thenAnswer((_) async => [msPaintWindow]);
526+
when(storage.getValue('favorites')).thenAnswer(
527+
(_) async => <String>[msPaintProcess.executable],
528+
);
529+
await cubit.manualRefresh();
530+
expect(state.windows.first.process.isFavorite, true);
531+
});
532+
});
533+
534+
group('refreshWindows:', () {
535+
test('manualRefresh fetches updated window list from NativePlatform', () async {
536+
when(
537+
nativePlatform.windows(showHidden: anyNamed('showHidden')),
538+
).thenAnswer((_) async => [msPaintWindow]);
539+
await cubit.manualRefresh();
540+
expect(state.windows.length, 1);
541+
542+
when(
543+
nativePlatform.windows(showHidden: anyNamed('showHidden')),
544+
).thenAnswer((_) async => []);
545+
await cubit.manualRefresh();
546+
expect(state.windows.length, 0);
547+
});
548+
});
455549
});
456550
}

0 commit comments

Comments
 (0)