Main coves client
1import 'package:coves_flutter/models/community.dart';
2import 'package:coves_flutter/providers/auth_provider.dart';
3import 'package:coves_flutter/screens/home/create_post_screen.dart';
4import 'package:flutter/material.dart';
5import 'package:flutter_test/flutter_test.dart';
6import 'package:provider/provider.dart';
7
8// Fake AuthProvider for testing
9class FakeAuthProvider extends AuthProvider {
10 bool _isAuthenticated = true;
11 String? _did = 'did:plc:testuser';
12 String? _handle = 'testuser.coves.social';
13
14 @override
15 bool get isAuthenticated => _isAuthenticated;
16
17 @override
18 String? get did => _did;
19
20 @override
21 String? get handle => _handle;
22
23 void setAuthenticated({required bool value, String? did, String? handle}) {
24 _isAuthenticated = value;
25 _did = did;
26 _handle = handle;
27 notifyListeners();
28 }
29
30 @override
31 Future<String?> getAccessToken() async {
32 return _isAuthenticated ? 'mock_access_token' : null;
33 }
34
35 @override
36 Future<bool> refreshToken() async {
37 return _isAuthenticated;
38 }
39
40 @override
41 Future<void> signOut() async {
42 _isAuthenticated = false;
43 _did = null;
44 _handle = null;
45 notifyListeners();
46 }
47}
48
49void main() {
50 group('CreatePostScreen Widget Tests', () {
51 late FakeAuthProvider fakeAuthProvider;
52
53 setUp(() {
54 fakeAuthProvider = FakeAuthProvider();
55 });
56
57 Widget createTestWidget({VoidCallback? onNavigateToFeed}) {
58 return MultiProvider(
59 providers: [
60 ChangeNotifierProvider<AuthProvider>.value(value: fakeAuthProvider),
61 ],
62 child: MaterialApp(
63 home: CreatePostScreen(onNavigateToFeed: onNavigateToFeed),
64 ),
65 );
66 }
67
68 testWidgets('should display Create Post title', (tester) async {
69 await tester.pumpWidget(createTestWidget());
70 await tester.pumpAndSettle();
71
72 expect(find.text('Create Post'), findsOneWidget);
73 });
74
75 testWidgets('should display user handle', (tester) async {
76 await tester.pumpWidget(createTestWidget());
77 await tester.pumpAndSettle();
78
79 expect(find.text('@testuser.coves.social'), findsOneWidget);
80 });
81
82 testWidgets('should display community selector', (tester) async {
83 await tester.pumpWidget(createTestWidget());
84 await tester.pumpAndSettle();
85
86 expect(find.text('Select a community'), findsOneWidget);
87 });
88
89 testWidgets('should display title field', (tester) async {
90 await tester.pumpWidget(createTestWidget());
91 await tester.pumpAndSettle();
92
93 expect(find.widgetWithText(TextField, 'Title'), findsOneWidget);
94 });
95
96 testWidgets('should display URL field', (tester) async {
97 await tester.pumpWidget(createTestWidget());
98 await tester.pumpAndSettle();
99
100 expect(find.widgetWithText(TextField, 'URL'), findsOneWidget);
101 });
102
103 testWidgets('should display body field', (tester) async {
104 await tester.pumpWidget(createTestWidget());
105 await tester.pumpAndSettle();
106
107 expect(
108 find.widgetWithText(TextField, 'What are your thoughts?'),
109 findsOneWidget,
110 );
111 });
112
113 testWidgets('should display language dropdown', (tester) async {
114 await tester.pumpWidget(createTestWidget());
115 await tester.pumpAndSettle();
116
117 // Default language should be English
118 expect(find.text('English'), findsOneWidget);
119 });
120
121 testWidgets('should display NSFW toggle', (tester) async {
122 await tester.pumpWidget(createTestWidget());
123 await tester.pumpAndSettle();
124
125 expect(find.text('NSFW'), findsOneWidget);
126 expect(find.byType(Switch), findsOneWidget);
127 });
128
129 testWidgets('should have disabled Post button initially', (tester) async {
130 await tester.pumpWidget(createTestWidget());
131 await tester.pumpAndSettle();
132
133 // Find the Post button
134 final postButton = find.widgetWithText(TextButton, 'Post');
135 expect(postButton, findsOneWidget);
136
137 // Button should be disabled (no community selected, no content)
138 final button = tester.widget<TextButton>(postButton);
139 expect(button.onPressed, isNull);
140 });
141
142 testWidgets('should enable Post button when title is entered and community selected', (tester) async {
143 await tester.pumpWidget(createTestWidget());
144 await tester.pumpAndSettle();
145
146 // Enter a title
147 await tester.enterText(find.widgetWithText(TextField, 'Title'), 'Test Post');
148 await tester.pumpAndSettle();
149
150 // Post button should still be disabled (no community selected)
151 final postButton = find.widgetWithText(TextButton, 'Post');
152 final button = tester.widget<TextButton>(postButton);
153 expect(button.onPressed, isNull);
154 });
155
156 testWidgets('should toggle NSFW switch', (tester) async {
157 await tester.pumpWidget(createTestWidget());
158 await tester.pumpAndSettle();
159
160 // Find the switch
161 final switchWidget = find.byType(Switch);
162 expect(switchWidget, findsOneWidget);
163
164 // Initially should be off
165 Switch switchBefore = tester.widget<Switch>(switchWidget);
166 expect(switchBefore.value, false);
167
168 // Scroll to make switch visible, then tap
169 await tester.ensureVisible(switchWidget);
170 await tester.pumpAndSettle();
171 await tester.tap(switchWidget);
172 await tester.pumpAndSettle();
173
174 // Should be on now
175 Switch switchAfter = tester.widget<Switch>(switchWidget);
176 expect(switchAfter.value, true);
177 });
178
179 testWidgets('should show thumbnail field when URL is entered', (tester) async {
180 await tester.pumpWidget(createTestWidget());
181 await tester.pumpAndSettle();
182
183 // Initially no thumbnail field
184 expect(find.widgetWithText(TextField, 'Thumbnail URL'), findsNothing);
185
186 // Enter a URL
187 await tester.enterText(
188 find.widgetWithText(TextField, 'URL'),
189 'https://example.com',
190 );
191 await tester.pumpAndSettle();
192
193 // Thumbnail field should now be visible
194 expect(find.widgetWithText(TextField, 'Thumbnail URL'), findsOneWidget);
195 });
196
197 testWidgets('should hide thumbnail field when URL is cleared', (tester) async {
198 await tester.pumpWidget(createTestWidget());
199 await tester.pumpAndSettle();
200
201 // Enter a URL
202 final urlField = find.widgetWithText(TextField, 'URL');
203 await tester.enterText(urlField, 'https://example.com');
204 await tester.pumpAndSettle();
205
206 // Thumbnail field should be visible
207 expect(find.widgetWithText(TextField, 'Thumbnail URL'), findsOneWidget);
208
209 // Clear the URL
210 await tester.enterText(urlField, '');
211 await tester.pumpAndSettle();
212
213 // Thumbnail field should be hidden
214 expect(find.widgetWithText(TextField, 'Thumbnail URL'), findsNothing);
215 });
216
217 testWidgets('should display close button', (tester) async {
218 await tester.pumpWidget(createTestWidget());
219 await tester.pumpAndSettle();
220
221 expect(find.byIcon(Icons.close), findsOneWidget);
222 });
223
224 testWidgets('should call onNavigateToFeed when close button is tapped', (tester) async {
225 bool callbackCalled = false;
226
227 await tester.pumpWidget(
228 createTestWidget(onNavigateToFeed: () => callbackCalled = true),
229 );
230 await tester.pumpAndSettle();
231
232 await tester.tap(find.byIcon(Icons.close));
233 await tester.pumpAndSettle();
234
235 expect(callbackCalled, true);
236 });
237
238 testWidgets('should have character limit on title field', (tester) async {
239 await tester.pumpWidget(createTestWidget());
240 await tester.pumpAndSettle();
241
242 // Find the title TextField
243 final titleField = find.widgetWithText(TextField, 'Title');
244 final textField = tester.widget<TextField>(titleField);
245
246 // Should have maxLength set to 300 (kTitleMaxLength)
247 expect(textField.maxLength, 300);
248 });
249
250 testWidgets('should have character limit on body field', (tester) async {
251 await tester.pumpWidget(createTestWidget());
252 await tester.pumpAndSettle();
253
254 // Find the body TextField
255 final bodyField = find.widgetWithText(TextField, 'What are your thoughts?');
256 final textField = tester.widget<TextField>(bodyField);
257
258 // Should have maxLength set to 10000 (kContentMaxLength)
259 expect(textField.maxLength, 10000);
260 });
261
262 testWidgets('should be scrollable', (tester) async {
263 await tester.pumpWidget(createTestWidget());
264 await tester.pumpAndSettle();
265
266 // Should have a SingleChildScrollView
267 expect(find.byType(SingleChildScrollView), findsOneWidget);
268 });
269 });
270
271 group('CreatePostScreen Form Validation', () {
272 late FakeAuthProvider fakeAuthProvider;
273
274 setUp(() {
275 fakeAuthProvider = FakeAuthProvider();
276 });
277
278 Widget createTestWidget() {
279 return MultiProvider(
280 providers: [
281 ChangeNotifierProvider<AuthProvider>.value(value: fakeAuthProvider),
282 ],
283 child: const MaterialApp(home: CreatePostScreen()),
284 );
285 }
286
287 testWidgets('form is invalid with no community and no content', (tester) async {
288 await tester.pumpWidget(createTestWidget());
289 await tester.pumpAndSettle();
290
291 final postButton = find.widgetWithText(TextButton, 'Post');
292 final button = tester.widget<TextButton>(postButton);
293 expect(button.onPressed, isNull);
294 });
295
296 testWidgets('form is invalid with content but no community', (tester) async {
297 await tester.pumpWidget(createTestWidget());
298 await tester.pumpAndSettle();
299
300 // Enter title
301 await tester.enterText(find.widgetWithText(TextField, 'Title'), 'Test');
302 await tester.pumpAndSettle();
303
304 final postButton = find.widgetWithText(TextButton, 'Post');
305 final button = tester.widget<TextButton>(postButton);
306 expect(button.onPressed, isNull);
307 });
308
309 testWidgets('entering text updates form state', (tester) async {
310 await tester.pumpWidget(createTestWidget());
311 await tester.pumpAndSettle();
312
313 // Enter title
314 await tester.enterText(
315 find.widgetWithText(TextField, 'Title'),
316 'My Test Post',
317 );
318 await tester.pumpAndSettle();
319
320 // Verify text was entered
321 expect(find.text('My Test Post'), findsOneWidget);
322 });
323
324 testWidgets('entering body text updates form state', (tester) async {
325 await tester.pumpWidget(createTestWidget());
326 await tester.pumpAndSettle();
327
328 // Enter body
329 await tester.enterText(
330 find.widgetWithText(TextField, 'What are your thoughts?'),
331 'This is my post content',
332 );
333 await tester.pumpAndSettle();
334
335 // Verify text was entered
336 expect(find.text('This is my post content'), findsOneWidget);
337 });
338 });
339}