Skip to content

Commit 319ebec

Browse files
authored
Merge pull request #338 from sixtusagbo/add-ui-tests
Add UI tests for Riverpod providers
2 parents e6a93a3 + 088ab12 commit 319ebec

File tree

2 files changed

+334
-1
lines changed

2 files changed

+334
-1
lines changed

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies:
1313
multi_split_view: ^2.4.0
1414
url_launcher: ^6.2.5
1515
flutter_riverpod: ^2.5.1
16-
riverpod: ^2.5.1
16+
riverpod: ^2.5.1
1717
uuid: ^4.3.3
1818
davi: ^3.4.1
1919
http: ^1.2.1
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
import 'dart:io';
2+
3+
import 'package:apidash/providers/providers.dart';
4+
import 'package:apidash/screens/dashboard.dart';
5+
import 'package:apidash/screens/home_page/collection_pane.dart';
6+
import 'package:apidash/screens/home_page/home_page.dart';
7+
import 'package:apidash/screens/intro_page.dart';
8+
import 'package:apidash/screens/settings_page.dart';
9+
import 'package:apidash/services/hive_services.dart';
10+
import 'package:flutter/material.dart';
11+
import 'package:flutter/services.dart';
12+
import 'package:flutter_riverpod/flutter_riverpod.dart';
13+
import 'package:flutter_test/flutter_test.dart';
14+
15+
void main() {
16+
TestWidgetsFlutterBinding.ensureInitialized();
17+
18+
setUp(() async {
19+
const MethodChannel channel =
20+
MethodChannel('plugins.flutter.io/path_provider');
21+
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
22+
.setMockMethodCallHandler(channel, (MethodCall methodCall) async {
23+
if (methodCall.method == 'getApplicationDocumentsDirectory') {
24+
// Create a mock app doc directory for testing
25+
Directory tempDir =
26+
await Directory.systemTemp.createTemp('mock_app_doc_dir');
27+
return tempDir.path; // Return the path to the mock directory
28+
}
29+
return null;
30+
});
31+
await openBoxes();
32+
});
33+
34+
group('Testing navRailIndexStateProvider', () {
35+
testWidgets('Dashboard should display correct initial page',
36+
(tester) async {
37+
await tester.pumpWidget(
38+
const ProviderScope(
39+
child: MaterialApp(
40+
home: Dashboard(),
41+
),
42+
),
43+
);
44+
45+
// Verify that the HomePage is displayed initially
46+
expect(find.byType(HomePage), findsOneWidget);
47+
expect(find.byType(IntroPage), findsNothing);
48+
expect(find.byType(SettingsPage), findsNothing);
49+
});
50+
51+
testWidgets(
52+
"Dashboard should display IntroPage when navRailIndexStateProvider is 1",
53+
(WidgetTester tester) async {
54+
await tester.pumpWidget(
55+
ProviderScope(
56+
overrides: [
57+
navRailIndexStateProvider.overrideWith((ref) => 1),
58+
],
59+
child: const MaterialApp(
60+
home: Dashboard(),
61+
),
62+
),
63+
);
64+
65+
// Verify that the IntroPage is displayed
66+
expect(find.byType(IntroPage), findsOneWidget);
67+
expect(find.byType(HomePage), findsNothing);
68+
expect(find.byType(SettingsPage), findsNothing);
69+
});
70+
71+
testWidgets(
72+
"Dashboard should display SettingsPage when navRailIndexStateProvider is 2",
73+
(WidgetTester tester) async {
74+
await tester.pumpWidget(
75+
ProviderScope(
76+
overrides: [
77+
navRailIndexStateProvider.overrideWith((ref) => 2),
78+
],
79+
child: const MaterialApp(
80+
home: Dashboard(),
81+
),
82+
),
83+
);
84+
85+
// Verify that the SettingsPage is displayed
86+
expect(find.byType(SettingsPage), findsOneWidget);
87+
expect(find.byType(IntroPage), findsNothing);
88+
expect(find.byType(HomePage), findsNothing);
89+
});
90+
91+
testWidgets(
92+
'navRailIndexStateProvider should update when icon button is pressed',
93+
(tester) async {
94+
await tester.pumpWidget(
95+
const ProviderScope(
96+
child: MaterialApp(
97+
home: Dashboard(),
98+
),
99+
),
100+
);
101+
102+
// Tap on the Intro icon
103+
await tester.tap(find.byIcon(Icons.help_outline));
104+
await tester.pump();
105+
106+
// Verify that the navRailIndexStateProvider is updated
107+
final dashboard = tester.element(find.byType(Dashboard));
108+
final container = ProviderScope.containerOf(dashboard);
109+
expect(container.read(navRailIndexStateProvider), 1);
110+
});
111+
112+
testWidgets(
113+
'navRailIndexStateProvider should persist across widget rebuilds',
114+
(tester) async {
115+
// Pump the initial widget tree
116+
await tester.pumpWidget(
117+
const ProviderScope(
118+
child: MaterialApp(
119+
home: Dashboard(),
120+
),
121+
),
122+
);
123+
124+
// Tap on the Settings icon to change the index to 2
125+
await tester.tap(find.byIcon(Icons.settings_outlined));
126+
await tester.pump();
127+
128+
// Rebuild the widget tree with the same ProviderScope
129+
await tester.pumpWidget(
130+
const ProviderScope(
131+
child: MaterialApp(
132+
home: Dashboard(),
133+
),
134+
),
135+
);
136+
137+
// Verify that the navRailIndexStateProvider still has the updated value
138+
final dashboard = tester.element(find.byType(Dashboard));
139+
final container = ProviderScope.containerOf(dashboard);
140+
expect(container.read(navRailIndexStateProvider), 2);
141+
142+
// Verify that the SettingsPage is still displayed after the rebuild
143+
expect(find.byType(SettingsPage), findsOneWidget);
144+
expect(find.byType(IntroPage), findsNothing);
145+
expect(find.byType(HomePage), findsNothing);
146+
});
147+
148+
testWidgets(
149+
'UI should update correctly when navRailIndexStateProvider changes',
150+
(tester) async {
151+
await tester.pumpWidget(
152+
const ProviderScope(
153+
child: MaterialApp(
154+
home: Dashboard(),
155+
),
156+
),
157+
);
158+
159+
// Grab the Dashboard widget and its ProviderContainer
160+
final dashboard = tester.element(find.byType(Dashboard));
161+
final container = ProviderScope.containerOf(dashboard);
162+
163+
// Go to IntroPage
164+
container.read(navRailIndexStateProvider.notifier).state = 1;
165+
await tester.pump();
166+
167+
// Verify that the IntroPage is displayed
168+
expect(find.byType(IntroPage), findsOneWidget);
169+
// Verify that the selected icon is the filled version (selectedIcon)
170+
expect(find.byIcon(Icons.help), findsOneWidget);
171+
172+
// Go to SettingsPage
173+
container.read(navRailIndexStateProvider.notifier).state = 2;
174+
await tester.pump();
175+
176+
// Verify that the SettingsPage is displayed
177+
expect(find.byType(SettingsPage), findsOneWidget);
178+
// Verify that the selected icon is the filled version (selectedIcon)
179+
expect(find.byIcon(Icons.settings), findsOneWidget);
180+
});
181+
182+
testWidgets(
183+
'navRailIndexStateProvider should be disposed when Dashboard is removed',
184+
(tester) async {
185+
await tester.pumpWidget(
186+
const ProviderScope(
187+
child: MaterialApp(
188+
home: Dashboard(),
189+
),
190+
),
191+
);
192+
193+
// Grab the Dashboard widget and its ProviderContainer
194+
final dashboard = tester.element(find.byType(Dashboard));
195+
final container = ProviderScope.containerOf(dashboard);
196+
197+
// Pumping a different widget to remove the Dashboard from the widget tree
198+
await tester.pumpWidget(
199+
const MaterialApp(
200+
home: Scaffold(body: Text('Different Widget')),
201+
),
202+
);
203+
204+
// Verify that the ProviderContainer has been disposed
205+
// by trying to read from disposed container
206+
bool isDisposed = false;
207+
try {
208+
container.read(navRailIndexStateProvider);
209+
} catch (e) {
210+
isDisposed = true;
211+
}
212+
expect(isDisposed, true);
213+
});
214+
});
215+
216+
group("Testing selectedIdEditStateProvider", () {
217+
testWidgets(
218+
'selectedIdEditStateProvider should have an initial value of null',
219+
(tester) async {
220+
await tester.pumpWidget(
221+
const ProviderScope(
222+
child: MaterialApp(
223+
home: CollectionPane(),
224+
),
225+
),
226+
);
227+
228+
// Verify that the initial value is null
229+
final collectionPane = tester.element(find.byType(CollectionPane));
230+
final container = ProviderScope.containerOf(collectionPane);
231+
expect(container.read(selectedIdEditStateProvider), null);
232+
});
233+
234+
testWidgets(
235+
'selectedIdEditStateProvider should not be null after rename button has been tapped',
236+
(tester) async {
237+
await tester.pumpWidget(
238+
const ProviderScope(
239+
child: MaterialApp(
240+
home: CollectionPane(),
241+
),
242+
),
243+
);
244+
245+
// Tap on the three dots to open the request card menu
246+
await tester.tap(find.byType(RequestList));
247+
await tester.pump();
248+
await tester.tap(find.byType(RequestItem));
249+
await tester.pump();
250+
await tester.tap(find.byIcon(Icons.more_vert).first);
251+
await tester.pumpAndSettle();
252+
253+
// Tap on the "Rename" option in the menu
254+
await tester.tap(find.text('Rename'));
255+
await tester.pumpAndSettle();
256+
257+
// Verify that the selectedIdEditStateProvider is not null
258+
final collectionPane = tester.element(find.byType(CollectionPane));
259+
final container = ProviderScope.containerOf(collectionPane);
260+
expect(container.read(selectedIdEditStateProvider), isNotNull);
261+
expect((container.read(selectedIdEditStateProvider)).runtimeType, String);
262+
});
263+
264+
testWidgets(
265+
'It should be set back to null when user taps outside name editor',
266+
(tester) async {
267+
await tester.pumpWidget(
268+
const ProviderScope(
269+
child: MaterialApp(
270+
home: CollectionPane(),
271+
),
272+
),
273+
);
274+
275+
// Grab the CollectionPane widget and its ProviderContainer
276+
final collectionPane = tester.element(find.byType(CollectionPane));
277+
final container = ProviderScope.containerOf(collectionPane);
278+
279+
// Tap on the three dots to open the request card menu
280+
await tester.tap(find.byType(RequestList));
281+
await tester.pump();
282+
await tester.tap(find.byType(RequestItem));
283+
await tester.pump();
284+
await tester.tap(find.byIcon(Icons.more_vert).first);
285+
await tester.pumpAndSettle();
286+
287+
// Tap on the "Rename" option in the menu
288+
await tester.tap(find.text('Rename'));
289+
await tester.pumpAndSettle();
290+
291+
// Verify that the selectedIdEditStateProvider is not null
292+
expect(container.read(selectedIdEditStateProvider), isNotNull);
293+
expect((container.read(selectedIdEditStateProvider)).runtimeType, String);
294+
295+
// Tap on the screen to simulate tapping outside the name editor
296+
await tester.tap(find.byType(CollectionPane));
297+
await tester.pumpAndSettle();
298+
299+
// Verify that the selectedIdEditStateProvider is null
300+
expect(container.read(selectedIdEditStateProvider), null);
301+
});
302+
testWidgets("It should be properly disposed", (tester) async {
303+
await tester.pumpWidget(
304+
const ProviderScope(
305+
child: MaterialApp(
306+
home: CollectionPane(),
307+
),
308+
),
309+
);
310+
311+
// Grab the Dashboard widget and its ProviderContainer
312+
final collectionPane = tester.element(find.byType(CollectionPane));
313+
final container = ProviderScope.containerOf(collectionPane);
314+
315+
// Pumping a different widget to remove the CollectionPane from the widget tree
316+
await tester.pumpWidget(
317+
const MaterialApp(
318+
home: Scaffold(body: Text('Foo')),
319+
),
320+
);
321+
322+
// Verify that the ProviderContainer has been disposed
323+
// by trying to read from disposed container
324+
bool isDisposed = false;
325+
try {
326+
container.read(selectedIdEditStateProvider);
327+
} catch (e) {
328+
isDisposed = true;
329+
}
330+
expect(isDisposed, true);
331+
});
332+
});
333+
}

0 commit comments

Comments
 (0)