// Author: fruit.com.cn import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/models/comment.dart'; import 'package:fruitshorts/core/data/models/interaction.dart'; import 'package:fruitshorts/core/data/remote/api_client.dart'; import 'package:fruitshorts/core/data/services/comment_controller.dart'; import '../support/fake_interaction_repositories.dart'; void main() { CommentController controller( FakeCommentRepository comments, { FakeInteractionRepository? interactions, }) => CommentController( comments: comments, interactions: interactions ?? FakeInteractionRepository(), dramaId: '5', ); test('should_append_next_page_with_cursor_when_has_more_is_true', () async { final repository = FakeCommentRepository( pages: [ CommentPage( comments: [commentFixture(id: '2')], nextCursor: '2', hasMore: true, ), CommentPage(comments: [commentFixture(id: '1')]), ], ); final model = controller(repository); await model.load(); await model.loadMore(); expect(model.comments.map((item) => item.id), ['2', '1']); expect(model.hasMore, isFalse); expect(repository.pageCalls[0].cursor, isNull); expect(repository.pageCalls[1].cursor, '2'); expect(repository.pageCalls.every((call) => call.size == 20), isTrue); model.dispose(); }); test('should_use_server_content_and_pending_status_when_review_is_returned', () async { final repository = FakeCommentRepository( publishResult: commentFixture( id: '3', content: 'replaced ***', status: 'pending', mine: true, ), ); final model = controller(repository); final result = await model.publish(' replaced badword '); expect(result.isSuccess, isTrue); expect(repository.publishCalls.single.content, 'replaced badword'); expect(model.comments.single.content, 'replaced ***'); expect(model.comments.single.isPending, isTrue); model.dispose(); }); test('should_expose_backend_message_and_keep_existing_list_when_publish_is_blocked', () async { final repository = FakeCommentRepository( publishError: const ApiException(400, '评论包含敏感词'), ); final model = controller(repository); await model.load(); final result = await model.publish('blocked'); expect(result.isSuccess, isFalse); expect(result.errorMessage, '评论包含敏感词'); expect(model.comments, isEmpty); model.dispose(); }); test('should_insert_reply_under_root_and_request_replies_with_root_id', () async { final root = commentFixture(id: '10', replyCount: 1); final reply = commentFixture( id: '11', parentId: '10', rootId: '10', content: 'Reply', mine: true, ); final repository = FakeCommentRepository( pages: [ CommentPage(comments: [root]), CommentPage(comments: [reply]), ], publishResult: reply, ); final model = controller(repository); await model.load(); await model.loadReplies('10'); final result = await model.publish('Reply', replyTo: root); expect(result.isSuccess, isTrue); expect(repository.publishCalls.single.parentId, '10'); expect(repository.pageCalls.last.rootId, '10'); expect(model.repliesFor('10').map((item) => item.id), ['11']); model.dispose(); }); test('should_restore_comment_at_original_position_when_delete_fails', () async { final first = commentFixture(id: '2', mine: true); final second = commentFixture(id: '1'); final repository = FakeCommentRepository( pages: [ CommentPage(comments: [first, second]), ], deleteError: const ApiException(500, '删除失败'), ); final model = controller(repository); await model.load(); final result = await model.delete(first); expect(result.isSuccess, isFalse); expect(result.errorMessage, '删除失败'); expect(model.comments.map((item) => item.id), ['2', '1']); expect(repository.deleteCalls, ['2']); model.dispose(); }); test('should_remove_own_comment_when_delete_succeeds', () async { final own = commentFixture(id: '2', mine: true); final repository = FakeCommentRepository( pages: [ CommentPage(comments: [own]), ], ); final model = controller(repository); await model.load(); final result = await model.delete(own); expect(result.isSuccess, isTrue); expect(model.comments, isEmpty); model.dispose(); }); test('should_optimistically_update_comment_like_and_apply_server_count', () async { final comment = commentFixture(id: '2', likeCount: 3, liked: false); final interactions = FakeInteractionRepository( nextLikeResult: const LikeToggleResult(liked: true, count: 8), ); final repository = FakeCommentRepository( pages: [ CommentPage(comments: [comment]), ], ); final model = controller(repository, interactions: interactions); await model.load(); final pending = model.toggleLike(comment); expect(model.comments.single.liked, isTrue); expect(model.comments.single.likeCount, 4); final result = await pending; expect(result.isSuccess, isTrue); expect(model.comments.single.liked, isTrue); expect(model.comments.single.likeCount, 8); expect(interactions.likeTargets, ['COMMENT:2']); model.dispose(); }); test('should_rollback_comment_like_when_request_fails', () async { final comment = commentFixture(id: '2', likeCount: 3, liked: true); final interactions = FakeInteractionRepository() ..likeError = const ApiException(500, '点赞失败'); final repository = FakeCommentRepository( pages: [ CommentPage(comments: [comment]), ], ); final model = controller(repository, interactions: interactions); await model.load(); final result = await model.toggleLike(comment); expect(result.isSuccess, isFalse); expect(result.errorMessage, '点赞失败'); expect(model.comments.single.liked, isTrue); expect(model.comments.single.likeCount, 3); model.dispose(); }); }