at main 14 kB view raw
1import 'package:coves_flutter/models/post.dart'; 2import 'package:coves_flutter/services/streamable_service.dart'; 3import 'package:coves_flutter/widgets/post_card.dart'; 4import 'package:dio/dio.dart'; 5import 'package:flutter/material.dart'; 6import 'package:flutter_test/flutter_test.dart'; 7import 'package:http_mock_adapter/http_mock_adapter.dart'; 8import 'package:provider/provider.dart'; 9 10import '../test_helpers/mock_providers.dart'; 11 12void main() { 13 late MockAuthProvider mockAuthProvider; 14 late MockVoteProvider mockVoteProvider; 15 16 setUp(() { 17 mockAuthProvider = MockAuthProvider(); 18 mockVoteProvider = MockVoteProvider(); 19 }); 20 21 Widget createTestWidget( 22 FeedViewPost post, { 23 StreamableService? streamableService, 24 }) { 25 return MultiProvider( 26 providers: [ 27 // ignore: argument_type_not_assignable 28 ChangeNotifierProvider<MockAuthProvider>.value(value: mockAuthProvider), 29 // ignore: argument_type_not_assignable 30 ChangeNotifierProvider<MockVoteProvider>.value(value: mockVoteProvider), 31 Provider<StreamableService>.value( 32 value: streamableService ?? StreamableService(), 33 ), 34 ], 35 child: MaterialApp(home: Scaffold(body: PostCard(post: post))), 36 ); 37 } 38 39 group( 40 'PostCard', 41 skip: 'Provider type compatibility issues - needs mock refactoring', 42 () { 43 testWidgets('renders all basic components', (tester) async { 44 final post = FeedViewPost( 45 post: PostView( 46 uri: 'at://did:example/post/123', 47 cid: 'cid123', 48 rkey: '123', 49 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 50 community: CommunityRef( 51 did: 'did:plc:community', 52 name: 'test-community', 53 ), 54 createdAt: DateTime(2024), 55 indexedAt: DateTime(2024), 56 text: 'Test post content', 57 title: 'Test Post Title', 58 stats: PostStats( 59 upvotes: 10, 60 downvotes: 2, 61 score: 8, 62 commentCount: 5, 63 ), 64 ), 65 ); 66 67 await tester.pumpWidget(createTestWidget(post)); 68 69 // Verify title is displayed 70 expect(find.text('Test Post Title'), findsOneWidget); 71 72 // Verify community name is displayed 73 expect(find.text('c/test-community'), findsOneWidget); 74 75 // Verify author handle is displayed 76 expect(find.text('@author.test'), findsOneWidget); 77 78 // Verify text content is displayed 79 expect(find.text('Test post content'), findsOneWidget); 80 81 // Verify stats are displayed 82 expect(find.text('8'), findsOneWidget); // score 83 expect(find.text('5'), findsOneWidget); // comment count 84 }); 85 86 testWidgets('displays community avatar when available', (tester) async { 87 final post = FeedViewPost( 88 post: PostView( 89 uri: 'at://did:example/post/123', 90 cid: 'cid123', 91 rkey: '123', 92 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 93 community: CommunityRef( 94 did: 'did:plc:community', 95 name: 'test-community', 96 avatar: 'https://example.com/avatar.jpg', 97 ), 98 createdAt: DateTime(2024), 99 indexedAt: DateTime(2024), 100 text: '', 101 stats: PostStats( 102 upvotes: 0, 103 downvotes: 0, 104 score: 0, 105 commentCount: 0, 106 ), 107 ), 108 ); 109 110 await tester.pumpWidget(createTestWidget(post)); 111 await tester.pumpAndSettle(); 112 113 // Avatar image should be present 114 expect(find.byType(Image), findsWidgets); 115 }); 116 117 testWidgets('shows fallback avatar when no avatar URL', (tester) async { 118 final post = FeedViewPost( 119 post: PostView( 120 uri: 'at://did:example/post/123', 121 cid: 'cid123', 122 rkey: '123', 123 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 124 community: CommunityRef( 125 did: 'did:plc:community', 126 name: 'TestCommunity', 127 ), 128 createdAt: DateTime(2024), 129 indexedAt: DateTime(2024), 130 text: '', 131 stats: PostStats( 132 upvotes: 0, 133 downvotes: 0, 134 score: 0, 135 commentCount: 0, 136 ), 137 ), 138 ); 139 140 await tester.pumpWidget(createTestWidget(post)); 141 142 // Verify fallback shows first letter 143 expect(find.text('T'), findsOneWidget); 144 }); 145 146 testWidgets('displays external link bar when embed present', ( 147 tester, 148 ) async { 149 final post = FeedViewPost( 150 post: PostView( 151 uri: 'at://did:example/post/123', 152 cid: 'cid123', 153 rkey: '123', 154 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 155 community: CommunityRef( 156 did: 'did:plc:community', 157 name: 'test-community', 158 ), 159 createdAt: DateTime(2024), 160 indexedAt: DateTime(2024), 161 text: '', 162 stats: PostStats( 163 upvotes: 0, 164 downvotes: 0, 165 score: 0, 166 commentCount: 0, 167 ), 168 embed: PostEmbed( 169 type: 'social.coves.embed.external', 170 external: ExternalEmbed( 171 uri: 'https://example.com/article', 172 domain: 'example.com', 173 title: 'Example Article', 174 ), 175 data: const {}, 176 ), 177 ), 178 ); 179 180 await tester.pumpWidget(createTestWidget(post)); 181 182 // Verify external link bar is present 183 expect(find.text('example.com'), findsOneWidget); 184 expect(find.byIcon(Icons.open_in_new), findsOneWidget); 185 }); 186 187 testWidgets('displays embed image when available', (tester) async { 188 final post = FeedViewPost( 189 post: PostView( 190 uri: 'at://did:example/post/123', 191 cid: 'cid123', 192 rkey: '123', 193 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 194 community: CommunityRef( 195 did: 'did:plc:community', 196 name: 'test-community', 197 ), 198 createdAt: DateTime(2024), 199 indexedAt: DateTime(2024), 200 text: '', 201 stats: PostStats( 202 upvotes: 0, 203 downvotes: 0, 204 score: 0, 205 commentCount: 0, 206 ), 207 embed: PostEmbed( 208 type: 'social.coves.embed.external', 209 external: ExternalEmbed( 210 uri: 'https://example.com/article', 211 thumb: 'https://example.com/thumb.jpg', 212 ), 213 data: const {}, 214 ), 215 ), 216 ); 217 218 await tester.pumpWidget(createTestWidget(post)); 219 await tester.pump(); 220 221 // Embed image should be loading/present 222 expect(find.byType(Image), findsWidgets); 223 }); 224 225 testWidgets('renders without title', (tester) async { 226 final post = FeedViewPost( 227 post: PostView( 228 uri: 'at://did:example/post/123', 229 cid: 'cid123', 230 rkey: '123', 231 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 232 community: CommunityRef( 233 did: 'did:plc:community', 234 name: 'test-community', 235 ), 236 createdAt: DateTime(2024), 237 indexedAt: DateTime(2024), 238 text: 'Just body text', 239 stats: PostStats( 240 upvotes: 0, 241 downvotes: 0, 242 score: 0, 243 commentCount: 0, 244 ), 245 ), 246 ); 247 248 await tester.pumpWidget(createTestWidget(post)); 249 250 // Should render without errors 251 expect(find.text('Just body text'), findsOneWidget); 252 expect(find.text('c/test-community'), findsOneWidget); 253 }); 254 255 testWidgets('has action buttons', (tester) async { 256 final post = FeedViewPost( 257 post: PostView( 258 uri: 'at://did:example/post/123', 259 cid: 'cid123', 260 rkey: '123', 261 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 262 community: CommunityRef( 263 did: 'did:plc:community', 264 name: 'test-community', 265 ), 266 createdAt: DateTime(2024), 267 indexedAt: DateTime(2024), 268 text: '', 269 stats: PostStats( 270 upvotes: 0, 271 downvotes: 0, 272 score: 0, 273 commentCount: 0, 274 ), 275 ), 276 ); 277 278 await tester.pumpWidget(createTestWidget(post)); 279 280 // Verify action buttons are present 281 expect(find.byIcon(Icons.more_horiz), findsOneWidget); // menu 282 // Share, comment, and heart icons are custom widgets, verify by count 283 expect(find.byType(InkWell), findsWidgets); 284 }); 285 286 testWidgets('displays play button overlay for Streamable videos', ( 287 tester, 288 ) async { 289 final post = FeedViewPost( 290 post: PostView( 291 uri: 'at://did:example/post/123', 292 cid: 'cid123', 293 rkey: '123', 294 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 295 community: CommunityRef( 296 did: 'did:plc:community', 297 name: 'test-community', 298 ), 299 createdAt: DateTime(2024), 300 indexedAt: DateTime(2024), 301 text: '', 302 stats: PostStats( 303 upvotes: 0, 304 downvotes: 0, 305 score: 0, 306 commentCount: 0, 307 ), 308 embed: PostEmbed( 309 type: 'social.coves.embed.external', 310 external: ExternalEmbed( 311 uri: 'https://streamable.com/abc123', 312 thumb: 'https://example.com/thumb.jpg', 313 embedType: 'video', 314 provider: 'streamable', 315 ), 316 data: const {}, 317 ), 318 ), 319 ); 320 321 await tester.pumpWidget(createTestWidget(post)); 322 await tester.pump(); 323 324 // Verify play button is displayed 325 expect(find.byIcon(Icons.play_arrow), findsOneWidget); 326 }); 327 328 testWidgets( 329 'shows loading indicator when fetching video URL for Streamable', 330 (tester) async { 331 final dio = Dio(BaseOptions(baseUrl: 'https://api.streamable.com')); 332 final dioAdapter = DioAdapter(dio: dio); 333 final streamableService = StreamableService(dio: dio); 334 335 // Delay the response to test loading state 336 dioAdapter.onGet( 337 '/videos/abc123', 338 (server) => server.reply(200, { 339 'files': { 340 'mp4': {'url': '//cdn.streamable.com/video.mp4'}, 341 }, 342 }, delay: const Duration(milliseconds: 500)), 343 ); 344 345 final post = FeedViewPost( 346 post: PostView( 347 uri: 'at://did:example/post/123', 348 cid: 'cid123', 349 rkey: '123', 350 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 351 community: CommunityRef( 352 did: 'did:plc:community', 353 name: 'test-community', 354 ), 355 createdAt: DateTime(2024), 356 indexedAt: DateTime(2024), 357 text: '', 358 stats: PostStats( 359 upvotes: 0, 360 downvotes: 0, 361 score: 0, 362 commentCount: 0, 363 ), 364 embed: PostEmbed( 365 type: 'social.coves.embed.external', 366 external: ExternalEmbed( 367 uri: 'https://streamable.com/abc123', 368 thumb: 'https://example.com/thumb.jpg', 369 embedType: 'video', 370 provider: 'streamable', 371 ), 372 data: const {}, 373 ), 374 ), 375 ); 376 377 await tester.pumpWidget( 378 createTestWidget(post, streamableService: streamableService), 379 ); 380 await tester.pump(); 381 382 // Tap the play button 383 await tester.tap(find.byIcon(Icons.play_arrow)); 384 await tester.pump(); 385 386 // Verify loading indicator is displayed 387 expect(find.byType(CircularProgressIndicator), findsOneWidget); 388 }, 389 ); 390 391 testWidgets('does not show play button for non-video embeds', ( 392 tester, 393 ) async { 394 final post = FeedViewPost( 395 post: PostView( 396 uri: 'at://did:example/post/123', 397 cid: 'cid123', 398 rkey: '123', 399 author: AuthorView(did: 'did:plc:author', handle: 'author.test'), 400 community: CommunityRef( 401 did: 'did:plc:community', 402 name: 'test-community', 403 ), 404 createdAt: DateTime(2024), 405 indexedAt: DateTime(2024), 406 text: '', 407 stats: PostStats( 408 upvotes: 0, 409 downvotes: 0, 410 score: 0, 411 commentCount: 0, 412 ), 413 embed: PostEmbed( 414 type: 'social.coves.embed.external', 415 external: ExternalEmbed( 416 uri: 'https://example.com/article', 417 thumb: 'https://example.com/thumb.jpg', 418 ), 419 data: const {}, 420 ), 421 ), 422 ); 423 424 await tester.pumpWidget(createTestWidget(post)); 425 await tester.pump(); 426 427 // Verify play button is NOT displayed 428 expect(find.byIcon(Icons.play_arrow), findsNothing); 429 }); 430 }, 431 ); 432}