Main coves client
1import 'package:coves_flutter/providers/auth_provider.dart';
2import 'package:coves_flutter/providers/vote_provider.dart';
3import 'package:coves_flutter/services/api_exceptions.dart';
4import 'package:coves_flutter/services/vote_service.dart';
5import 'package:flutter_test/flutter_test.dart';
6import 'package:mockito/annotations.dart';
7import 'package:mockito/mockito.dart';
8
9import 'vote_provider_test.mocks.dart';
10
11// Generate mocks for VoteService and AuthProvider
12@GenerateMocks([VoteService, AuthProvider])
13void main() {
14 TestWidgetsFlutterBinding.ensureInitialized();
15
16 group('VoteProvider', () {
17 late VoteProvider voteProvider;
18 late MockVoteService mockVoteService;
19 late MockAuthProvider mockAuthProvider;
20
21 setUp(() {
22 mockVoteService = MockVoteService();
23 mockAuthProvider = MockAuthProvider();
24
25 // Default: user is authenticated
26 when(mockAuthProvider.isAuthenticated).thenReturn(true);
27
28 voteProvider = VoteProvider(
29 voteService: mockVoteService,
30 authProvider: mockAuthProvider,
31 );
32 });
33
34 tearDown(() {
35 voteProvider.dispose();
36 });
37
38 group('toggleVote', () {
39 const testPostUri = 'at://did:plc:test/social.coves.post.record/123';
40 const testPostCid = 'bafy2bzacepostcid123';
41
42 test('should create vote with optimistic update', () async {
43 // Mock successful API response
44 when(
45 mockVoteService.createVote(
46 postUri: anyNamed('postUri'),
47 postCid: anyNamed('postCid'),
48 direction: anyNamed('direction'),
49 ),
50 ).thenAnswer(
51 (_) async => const VoteResponse(
52 uri: 'at://did:plc:test/social.coves.feed.vote/456',
53 cid: 'bafy123',
54 rkey: '456',
55 deleted: false,
56 ),
57 );
58
59 var notificationCount = 0;
60 voteProvider.addListener(() {
61 notificationCount++;
62 });
63
64 // Initially not liked
65 expect(voteProvider.isLiked(testPostUri), false);
66
67 // Toggle vote
68 final wasLiked = await voteProvider.toggleVote(
69 postUri: testPostUri,
70 postCid: testPostCid,
71 );
72
73 // Should return true (vote created)
74 expect(wasLiked, true);
75
76 // Should be liked now
77 expect(voteProvider.isLiked(testPostUri), true);
78
79 // Should have notified listeners twice (optimistic + server response)
80 expect(notificationCount, greaterThanOrEqualTo(2));
81
82 // Vote state should be correct
83 final voteState = voteProvider.getVoteState(testPostUri);
84 expect(voteState?.direction, 'up');
85 expect(voteState?.uri, 'at://did:plc:test/social.coves.feed.vote/456');
86 expect(voteState?.deleted, false);
87 });
88
89 test('should remove vote when toggled off', () async {
90 // First, set up initial vote state
91 voteProvider.setInitialVoteState(
92 postUri: testPostUri,
93 voteDirection: 'up',
94 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
95 );
96
97 expect(voteProvider.isLiked(testPostUri), true);
98
99 // Mock API response for toggling off
100 when(
101 mockVoteService.createVote(
102 postUri: anyNamed('postUri'),
103 postCid: anyNamed('postCid'),
104 direction: anyNamed('direction'),
105 ),
106 ).thenAnswer((_) async => const VoteResponse(deleted: true));
107
108 // Toggle vote off
109 final wasLiked = await voteProvider.toggleVote(
110 postUri: testPostUri,
111 postCid: testPostCid,
112 );
113
114 // Should return false (vote removed)
115 expect(wasLiked, false);
116
117 // Should not be liked anymore
118 expect(voteProvider.isLiked(testPostUri), false);
119
120 // Vote state should be marked as deleted
121 final voteState = voteProvider.getVoteState(testPostUri);
122 expect(voteState?.deleted, true);
123 });
124
125 test('should rollback on API error', () async {
126 // Set up initial state (not voted)
127 expect(voteProvider.isLiked(testPostUri), false);
128
129 // Mock API failure
130 when(
131 mockVoteService.createVote(
132 postUri: anyNamed('postUri'),
133 postCid: anyNamed('postCid'),
134 direction: anyNamed('direction'),
135 ),
136 ).thenThrow(ApiException('Network error', statusCode: 500));
137
138 var notificationCount = 0;
139 voteProvider.addListener(() {
140 notificationCount++;
141 });
142
143 // Try to toggle vote
144 expect(
145 () => voteProvider.toggleVote(
146 postUri: testPostUri,
147 postCid: testPostCid,
148 ),
149 throwsA(isA<ApiException>()),
150 );
151
152 // Should rollback to initial state (not liked)
153 await Future.delayed(Duration.zero); // Wait for async completion
154 expect(voteProvider.isLiked(testPostUri), false);
155 expect(voteProvider.getVoteState(testPostUri), null);
156
157 // Should have notified listeners (optimistic + rollback)
158 expect(notificationCount, greaterThanOrEqualTo(2));
159 });
160
161 test('should rollback to previous state on error', () async {
162 // Set up initial voted state
163 voteProvider.setInitialVoteState(
164 postUri: testPostUri,
165 voteDirection: 'up',
166 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
167 );
168
169 final initialState = voteProvider.getVoteState(testPostUri);
170 expect(voteProvider.isLiked(testPostUri), true);
171
172 // Mock API failure when trying to toggle off
173 when(
174 mockVoteService.createVote(
175 postUri: anyNamed('postUri'),
176 postCid: anyNamed('postCid'),
177 direction: anyNamed('direction'),
178 ),
179 ).thenThrow(NetworkException('Connection failed'));
180
181 // Try to toggle vote off
182 expect(
183 () => voteProvider.toggleVote(
184 postUri: testPostUri,
185 postCid: testPostCid,
186 ),
187 throwsA(isA<ApiException>()),
188 );
189
190 // Should rollback to initial liked state
191 await Future.delayed(Duration.zero); // Wait for async completion
192 expect(voteProvider.isLiked(testPostUri), true);
193 expect(voteProvider.getVoteState(testPostUri)?.uri, initialState?.uri);
194 });
195
196 test('should prevent concurrent requests for same post', () async {
197 // Mock slow API response
198 when(
199 mockVoteService.createVote(
200 postUri: anyNamed('postUri'),
201 postCid: anyNamed('postCid'),
202 direction: anyNamed('direction'),
203 ),
204 ).thenAnswer((_) async {
205 await Future.delayed(const Duration(milliseconds: 100));
206 return const VoteResponse(
207 uri: 'at://did:plc:test/social.coves.feed.vote/456',
208 cid: 'bafy123',
209 rkey: '456',
210 deleted: false,
211 );
212 });
213
214 // Start first request
215 final future1 = voteProvider.toggleVote(
216 postUri: testPostUri,
217 postCid: testPostCid,
218 );
219
220 // Try to start second request before first completes
221 final result2 = await voteProvider.toggleVote(
222 postUri: testPostUri,
223 postCid: testPostCid,
224 );
225
226 // Second request should be ignored
227 expect(result2, false);
228
229 // First request should complete normally
230 final result1 = await future1;
231 expect(result1, true);
232
233 // Should have only called API once
234 verify(
235 mockVoteService.createVote(
236 postUri: anyNamed('postUri'),
237 postCid: anyNamed('postCid'),
238 direction: anyNamed('direction'),
239 ),
240 ).called(1);
241 });
242
243 test('should handle downvote direction', () async {
244 when(
245 mockVoteService.createVote(
246 postUri: anyNamed('postUri'),
247 postCid: anyNamed('postCid'),
248 direction: anyNamed('direction'),
249 ),
250 ).thenAnswer(
251 (_) async => const VoteResponse(
252 uri: 'at://did:plc:test/social.coves.feed.vote/456',
253 cid: 'bafy123',
254 rkey: '456',
255 deleted: false,
256 ),
257 );
258
259 await voteProvider.toggleVote(
260 postUri: testPostUri,
261 postCid: testPostCid,
262 direction: 'down',
263 );
264
265 final voteState = voteProvider.getVoteState(testPostUri);
266 expect(voteState?.direction, 'down');
267 expect(voteState?.deleted, false);
268
269 // Should not be "liked" (isLiked checks for 'up' direction)
270 expect(voteProvider.isLiked(testPostUri), false);
271 });
272 });
273
274 group('setInitialVoteState', () {
275 const testPostUri = 'at://did:plc:test/social.coves.post.record/123';
276
277 test('should set initial vote state from API data', () {
278 voteProvider.setInitialVoteState(
279 postUri: testPostUri,
280 voteDirection: 'up',
281 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
282 );
283
284 expect(voteProvider.isLiked(testPostUri), true);
285
286 final voteState = voteProvider.getVoteState(testPostUri);
287 expect(voteState?.direction, 'up');
288 expect(voteState?.uri, 'at://did:plc:test/social.coves.feed.vote/456');
289 expect(voteState?.deleted, false);
290 });
291
292 test('should set initial vote state with "down" direction', () {
293 voteProvider.setInitialVoteState(
294 postUri: testPostUri,
295 voteDirection: 'down',
296 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
297 );
298
299 // Should not be "liked" (isLiked checks for 'up' direction)
300 expect(voteProvider.isLiked(testPostUri), false);
301
302 final voteState = voteProvider.getVoteState(testPostUri);
303 expect(voteState?.direction, 'down');
304 expect(voteState?.uri, 'at://did:plc:test/social.coves.feed.vote/456');
305 expect(voteState?.deleted, false);
306 });
307
308 test('should extract rkey from voteUri', () {
309 voteProvider.setInitialVoteState(
310 postUri: testPostUri,
311 voteDirection: 'up',
312 voteUri: 'at://did:plc:test/social.coves.feed.vote/3kbyxyz123',
313 );
314
315 final voteState = voteProvider.getVoteState(testPostUri);
316 expect(voteState?.rkey, '3kbyxyz123');
317 });
318
319 test('should handle voteUri being null', () {
320 voteProvider.setInitialVoteState(
321 postUri: testPostUri,
322 voteDirection: 'up',
323 );
324
325 final voteState = voteProvider.getVoteState(testPostUri);
326 expect(voteState?.direction, 'up');
327 expect(voteState?.uri, null);
328 expect(voteState?.rkey, null);
329 expect(voteState?.deleted, false);
330 });
331
332 test('should remove vote state when voteDirection is null', () {
333 // First set a vote
334 voteProvider.setInitialVoteState(
335 postUri: testPostUri,
336 voteDirection: 'up',
337 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
338 );
339
340 expect(voteProvider.isLiked(testPostUri), true);
341
342 // Then clear it
343 voteProvider.setInitialVoteState(postUri: testPostUri);
344
345 expect(voteProvider.isLiked(testPostUri), false);
346 expect(voteProvider.getVoteState(testPostUri), null);
347 });
348
349 test('should clear stale vote state when refreshing with null vote', () {
350 // Simulate initial state from previous session
351 voteProvider.setInitialVoteState(
352 postUri: testPostUri,
353 voteDirection: 'up',
354 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
355 );
356
357 expect(voteProvider.isLiked(testPostUri), true);
358
359 // Simulate refresh where server returns viewer.vote = null
360 // (user removed vote on another device)
361 voteProvider.setInitialVoteState(
362 postUri: testPostUri,
363 voteDirection: null,
364 );
365
366 // Vote should be cleared
367 expect(voteProvider.isLiked(testPostUri), false);
368 expect(voteProvider.getVoteState(testPostUri), null);
369 });
370
371 test('should clear stale score adjustment on refresh', () async {
372 // Simulate optimistic upvote that created a +1 adjustment
373 when(
374 mockVoteService.createVote(
375 postUri: anyNamed('postUri'),
376 postCid: anyNamed('postCid'),
377 direction: anyNamed('direction'),
378 ),
379 ).thenAnswer(
380 (_) async => const VoteResponse(
381 uri: 'at://did:plc:test/social.coves.feed.vote/456',
382 cid: 'bafy123',
383 rkey: '456',
384 deleted: false,
385 ),
386 );
387
388 // Create vote - this sets _scoreAdjustments[testPostUri] = +1
389 await voteProvider.toggleVote(
390 postUri: testPostUri,
391 postCid: 'bafy2bzacepostcid123',
392 );
393
394 // Verify adjustment exists
395 const serverScore = 10;
396 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 11);
397
398 // Now simulate a feed refresh - server returns fresh score (11)
399 // which already includes the vote. The adjustment should be cleared.
400 voteProvider.setInitialVoteState(
401 postUri: testPostUri,
402 voteDirection: 'up',
403 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
404 );
405
406 // After refresh, adjustment should be cleared (server score is truth)
407 // If we pass the NEW server score (11), we should get 11, not 12
408 const freshServerScore = 11;
409 expect(
410 voteProvider.getAdjustedScore(testPostUri, freshServerScore),
411 11,
412 );
413 });
414
415 test('should not notify listeners when setting initial state', () {
416 var notificationCount = 0;
417 voteProvider
418 ..addListener(() {
419 notificationCount++;
420 })
421 ..setInitialVoteState(
422 postUri: testPostUri,
423 voteDirection: 'up',
424 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
425 );
426
427 // Should NOT notify listeners (silent initialization)
428 expect(notificationCount, 0);
429 });
430 });
431
432 group('VoteState.extractRkeyFromUri', () {
433 test('should extract rkey from valid AT-URI', () {
434 expect(
435 VoteState.extractRkeyFromUri(
436 'at://did:plc:test/social.coves.feed.vote/3kbyxyz123',
437 ),
438 '3kbyxyz123',
439 );
440 });
441
442 test('should return null for null uri', () {
443 expect(VoteState.extractRkeyFromUri(null), null);
444 });
445
446 test('should handle URI with no path segments', () {
447 expect(VoteState.extractRkeyFromUri(''), '');
448 });
449
450 test('should handle complex rkey values', () {
451 expect(
452 VoteState.extractRkeyFromUri(
453 'at://did:plc:abc123xyz/social.coves.feed.vote/3lbp7kw2abc',
454 ),
455 '3lbp7kw2abc',
456 );
457 });
458 });
459
460 group('clear', () {
461 test('should clear all vote state', () {
462 const post1 = 'at://did:plc:test/social.coves.post.record/1';
463 const post2 = 'at://did:plc:test/social.coves.post.record/2';
464
465 // Set up multiple votes
466 voteProvider
467 ..setInitialVoteState(
468 postUri: post1,
469 voteDirection: 'up',
470 voteUri: 'at://did:plc:test/social.coves.feed.vote/1',
471 )
472 ..setInitialVoteState(
473 postUri: post2,
474 voteDirection: 'up',
475 voteUri: 'at://did:plc:test/social.coves.feed.vote/2',
476 );
477
478 expect(voteProvider.isLiked(post1), true);
479 expect(voteProvider.isLiked(post2), true);
480
481 // Clear all
482 voteProvider.clear();
483
484 // Should have no votes
485 expect(voteProvider.isLiked(post1), false);
486 expect(voteProvider.isLiked(post2), false);
487 expect(voteProvider.getVoteState(post1), null);
488 expect(voteProvider.getVoteState(post2), null);
489 });
490
491 test('should notify listeners when cleared', () {
492 var notificationCount = 0;
493 voteProvider
494 ..addListener(() {
495 notificationCount++;
496 })
497 ..clear();
498
499 expect(notificationCount, 1);
500 });
501 });
502
503 group('isPending', () {
504 const testPostUri = 'at://did:plc:test/social.coves.post.record/123';
505 const testPostCid = 'bafy2bzacepostcid123';
506
507 test('should return true while request is in progress', () async {
508 // Mock slow API response
509 when(
510 mockVoteService.createVote(
511 postUri: anyNamed('postUri'),
512 postCid: anyNamed('postCid'),
513 direction: anyNamed('direction'),
514 ),
515 ).thenAnswer((_) async {
516 await Future.delayed(const Duration(milliseconds: 50));
517 return const VoteResponse(
518 uri: 'at://did:plc:test/social.coves.feed.vote/456',
519 cid: 'bafy123',
520 rkey: '456',
521 deleted: false,
522 );
523 });
524
525 expect(voteProvider.isPending(testPostUri), false);
526
527 // Start request
528 final future = voteProvider.toggleVote(
529 postUri: testPostUri,
530 postCid: testPostCid,
531 );
532
533 // Give it time to set pending flag
534 await Future.delayed(const Duration(milliseconds: 10));
535
536 // Should be pending now
537 expect(voteProvider.isPending(testPostUri), true);
538
539 // Wait for completion
540 await future;
541
542 // Should not be pending anymore
543 expect(voteProvider.isPending(testPostUri), false);
544 });
545
546 test('should return false for posts with no pending request', () {
547 const testPostUri = 'at://did:plc:test/social.coves.post.record/123';
548 expect(voteProvider.isPending(testPostUri), false);
549 });
550 });
551
552 group('Score adjustments', () {
553 const testPostUri = 'at://did:plc:test/social.coves.post.record/123';
554 const testPostCid = 'bafy2bzacepostcid123';
555
556 test('should adjust score when creating upvote', () async {
557 when(
558 mockVoteService.createVote(
559 postUri: anyNamed('postUri'),
560 postCid: anyNamed('postCid'),
561 direction: anyNamed('direction'),
562 ),
563 ).thenAnswer(
564 (_) async => const VoteResponse(
565 uri: 'at://did:plc:test/social.coves.feed.vote/456',
566 cid: 'bafy123',
567 rkey: '456',
568 deleted: false,
569 ),
570 );
571
572 // Initial score from server
573 const serverScore = 10;
574
575 // Before vote, adjustment should be 0
576 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 10);
577
578 // Create upvote
579 await voteProvider.toggleVote(
580 postUri: testPostUri,
581 postCid: testPostCid,
582 );
583
584 // Should have +1 adjustment (upvote added)
585 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 11);
586 });
587
588 test('should adjust score when removing upvote', () async {
589 // Set initial state with upvote
590 voteProvider.setInitialVoteState(
591 postUri: testPostUri,
592 voteDirection: 'up',
593 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
594 );
595
596 when(
597 mockVoteService.createVote(
598 postUri: anyNamed('postUri'),
599 postCid: anyNamed('postCid'),
600 direction: anyNamed('direction'),
601 ),
602 ).thenAnswer((_) async => const VoteResponse(deleted: true));
603
604 const serverScore = 10;
605
606 // Before removing, adjustment should be 0 (server knows about upvote)
607 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 10);
608
609 // Remove upvote
610 await voteProvider.toggleVote(
611 postUri: testPostUri,
612 postCid: testPostCid,
613 );
614
615 // Should have -1 adjustment (upvote removed)
616 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 9);
617 });
618
619 test('should adjust score when creating downvote', () async {
620 when(
621 mockVoteService.createVote(
622 postUri: anyNamed('postUri'),
623 postCid: anyNamed('postCid'),
624 direction: anyNamed('direction'),
625 ),
626 ).thenAnswer(
627 (_) async => const VoteResponse(
628 uri: 'at://did:plc:test/social.coves.feed.vote/456',
629 cid: 'bafy123',
630 rkey: '456',
631 deleted: false,
632 ),
633 );
634
635 const serverScore = 10;
636
637 // Create downvote
638 await voteProvider.toggleVote(
639 postUri: testPostUri,
640 postCid: testPostCid,
641 direction: 'down',
642 );
643
644 // Should have -1 adjustment (downvote added)
645 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 9);
646 });
647
648 test(
649 'should adjust score when switching from upvote to downvote',
650 () async {
651 // Set initial state with upvote
652 voteProvider.setInitialVoteState(
653 postUri: testPostUri,
654 voteDirection: 'up',
655 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
656 );
657
658 when(
659 mockVoteService.createVote(
660 postUri: anyNamed('postUri'),
661 postCid: anyNamed('postCid'),
662 direction: anyNamed('direction'),
663 ),
664 ).thenAnswer(
665 (_) async => const VoteResponse(
666 uri: 'at://did:plc:test/social.coves.feed.vote/789',
667 cid: 'bafy789',
668 rkey: '789',
669 deleted: false,
670 ),
671 );
672
673 const serverScore = 10;
674
675 // Switch to downvote
676 await voteProvider.toggleVote(
677 postUri: testPostUri,
678 postCid: testPostCid,
679 direction: 'down',
680 );
681
682 // Should have -2 adjustment (remove +1, add -1)
683 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 8);
684 },
685 );
686
687 test(
688 'should adjust score when switching from downvote to upvote',
689 () async {
690 // Set initial state with downvote
691 voteProvider.setInitialVoteState(
692 postUri: testPostUri,
693 voteDirection: 'down',
694 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
695 );
696
697 when(
698 mockVoteService.createVote(
699 postUri: anyNamed('postUri'),
700 postCid: anyNamed('postCid'),
701 direction: anyNamed('direction'),
702 ),
703 ).thenAnswer(
704 (_) async => const VoteResponse(
705 uri: 'at://did:plc:test/social.coves.feed.vote/789',
706 cid: 'bafy789',
707 rkey: '789',
708 deleted: false,
709 ),
710 );
711
712 const serverScore = 10;
713
714 // Switch to upvote
715 await voteProvider.toggleVote(
716 postUri: testPostUri,
717 postCid: testPostCid,
718 );
719
720 // Should have +2 adjustment (remove -1, add +1)
721 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 12);
722 },
723 );
724
725 test('should rollback score adjustment on error', () async {
726 const serverScore = 10;
727
728 when(
729 mockVoteService.createVote(
730 postUri: anyNamed('postUri'),
731 postCid: anyNamed('postCid'),
732 direction: anyNamed('direction'),
733 ),
734 ).thenThrow(ApiException('Network error', statusCode: 500));
735
736 // Try to vote (will fail)
737 expect(
738 () => voteProvider.toggleVote(
739 postUri: testPostUri,
740 postCid: testPostCid,
741 ),
742 throwsA(isA<ApiException>()),
743 );
744
745 await Future.delayed(Duration.zero);
746
747 // Adjustment should be rolled back to 0
748 expect(voteProvider.getAdjustedScore(testPostUri, serverScore), 10);
749 });
750
751 test('should clear score adjustments when clearing all state', () {
752 const testPostUri1 = 'at://did:plc:test/social.coves.post.record/1';
753 const testPostUri2 = 'at://did:plc:test/social.coves.post.record/2';
754
755 // Manually set some adjustments (simulating votes)
756 voteProvider
757 ..setInitialVoteState(
758 postUri: testPostUri1,
759 voteDirection: 'up',
760 voteUri: 'at://did:plc:test/social.coves.feed.vote/1',
761 )
762 ..clear();
763
764 // Adjustments should be cleared (back to 0)
765 expect(voteProvider.getAdjustedScore(testPostUri1, 10), 10);
766 expect(voteProvider.getAdjustedScore(testPostUri2, 5), 5);
767 });
768 });
769
770 group('Auth state listener', () {
771 test('should clear votes when user signs out', () {
772 const testPostUri = 'at://did:plc:test/social.coves.post.record/123';
773
774 // Set up vote state
775 voteProvider.setInitialVoteState(
776 postUri: testPostUri,
777 voteDirection: 'up',
778 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
779 );
780
781 expect(voteProvider.isLiked(testPostUri), true);
782
783 // Simulate sign out by changing auth state
784 when(mockAuthProvider.isAuthenticated).thenReturn(false);
785
786 // Trigger the auth listener by calling it directly
787 // (In real app, this would be triggered by
788 // AuthProvider.notifyListeners)
789 voteProvider.clear();
790
791 // Votes should be cleared
792 expect(voteProvider.isLiked(testPostUri), false);
793 expect(voteProvider.getVoteState(testPostUri), null);
794 });
795
796 test('should not clear votes when user is still authenticated', () {
797 const testPostUri = 'at://did:plc:test/social.coves.post.record/123';
798
799 // Set up vote state
800 voteProvider.setInitialVoteState(
801 postUri: testPostUri,
802 voteDirection: 'up',
803 voteUri: 'at://did:plc:test/social.coves.feed.vote/456',
804 );
805
806 expect(voteProvider.isLiked(testPostUri), true);
807
808 // Auth state remains authenticated
809 when(mockAuthProvider.isAuthenticated).thenReturn(true);
810
811 // Votes should NOT be cleared
812 expect(voteProvider.isLiked(testPostUri), true);
813 });
814 });
815 });
816}