// Author: fruit.com.cn import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/models/comment.dart'; import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/models/interaction.dart'; import 'package:fruitshorts/core/data/remote/api_client.dart'; import 'package:fruitshorts/core/data/remote/auth_api.dart'; import 'package:fruitshorts/core/data/repositories/comment_repository.dart'; import 'package:fruitshorts/core/data/repositories/interaction_repository.dart'; import 'package:fruitshorts/core/data/services/auth_controller.dart'; import 'package:fruitshorts/core/data/services/auth_session_store.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/player/widgets/drama_comments_sheet.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; import '../support/fake_http_adapter.dart'; import '../support/fake_interaction_repositories.dart'; const _drama = Drama( id: '5', title: 'The Signal', synopsis: 'synopsis', posterUrl: '/files/5.jpg', ); void main() { setUp(() { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.withData({ 'auth.accessToken': 'test-access', 'auth.refreshToken': 'test-refresh', 'auth.user': '{"uid":"9","nickname":"Me","avatarUrl":""}', }); }); testWidgets('should_show_server_comments_and_append_next_cursor_page', ( tester, ) async { final repository = FakeCommentRepository( pages: [ CommentPage( comments: [ commentFixture(id: '10', content: 'Great episode', replyCount: 1), ], nextCursor: '10', hasMore: true, ), CommentPage( comments: [commentFixture(id: '9', content: 'Second page')], ), ], ); await _pumpSheet(tester, repository: repository); expect(find.text('Great episode'), findsOneWidget); expect(find.byKey(const Key('comment-list-load-more')), findsOneWidget); await tester.tap(find.byKey(const Key('comment-list-load-more'))); await tester.pumpAndSettle(); expect(find.text('Second page'), findsOneWidget); expect(repository.pageCalls.last.cursor, '10'); }); testWidgets('should_keep_input_and_show_backend_message_when_comment_is_blocked', ( tester, ) async { final repository = FakeCommentRepository( pages: [const CommentPage()], publishError: const ApiException(400, '评论包含敏感词'), ); await _pumpSheet(tester, repository: repository); await tester.enterText( find.byKey(const Key('comment-input')), 'blocked text', ); await tester.tap(find.byKey(const Key('comment-post'))); await tester.pumpAndSettle(); expect(find.text('评论包含敏感词'), findsOneWidget); final field = tester.widget( find.byKey(const Key('comment-input')), ); expect(field.controller?.text, 'blocked text'); }); testWidgets('should_show_review_badge_and_server_replaced_content', ( tester, ) async { final repository = FakeCommentRepository( pages: [const CommentPage()], publishResult: commentFixture( id: '11', content: 'hello ***', status: 'pending', mine: true, ), ); await _pumpSheet(tester, repository: repository); await tester.enterText( find.byKey(const Key('comment-input')), 'hello badword', ); await tester.tap(find.byKey(const Key('comment-post'))); await tester.pumpAndSettle(); expect(find.text('hello ***'), findsOneWidget); expect(find.text('Under review'), findsOneWidget); final field = tester.widget( find.byKey(const Key('comment-input')), ); expect(field.controller?.text, isEmpty); }); testWidgets('should_send_parent_id_when_replying_to_top_level_comment', ( tester, ) async { final root = commentFixture(id: '10', content: 'Root', replyCount: 0); final repository = FakeCommentRepository( pages: [ CommentPage(comments: [root]), ], publishResult: commentFixture( id: '11', parentId: '10', rootId: '10', content: 'My reply', mine: true, ), ); await _pumpSheet(tester, repository: repository); await tester.tap(find.byKey(const Key('comment-reply-10'))); await tester.pump(); expect(find.textContaining('Replying to'), findsOneWidget); await tester.enterText( find.byKey(const Key('comment-input')), 'My reply', ); await tester.tap(find.byKey(const Key('comment-post'))); await tester.pumpAndSettle(); expect(repository.publishCalls.single.parentId, '10'); expect(find.text('My reply'), findsOneWidget); }); testWidgets('should_show_error_and_retry_when_comment_page_fails', ( tester, ) async { final repository = FakeCommentRepository( pageError: StateError('offline'), ); await _pumpSheet(tester, repository: repository); expect(find.byKey(const Key('comment-list-error')), findsOneWidget); expect(find.byKey(const Key('comment-list-retry')), findsOneWidget); }); testWidgets('should_update_comment_like_count_when_server_returns_count', ( tester, ) async { final repository = FakeCommentRepository( pages: [ CommentPage( comments: [commentFixture(id: '10', content: 'Like me')], ), ], ); final interactions = FakeInteractionRepository() ..nextLikeResult = const LikeToggleResult(liked: true, count: 7); await _pumpSheet( tester, repository: repository, interactions: interactions, ); await tester.tap(find.byKey(const Key('comment-like-10'))); await tester.pumpAndSettle(); expect(interactions.likeTargets, ['COMMENT:10']); expect(find.text('7'), findsOneWidget); }); testWidgets('should_report_comment_and_show_success_when_submitted', ( tester, ) async { final repository = FakeCommentRepository( pages: [ CommentPage( comments: [commentFixture(id: '10', content: 'Report me')], ), ], ); final interactions = FakeInteractionRepository(); await _pumpSheet( tester, repository: repository, interactions: interactions, ); await tester.tap(find.byKey(const Key('comment-menu-10'))); await tester.pumpAndSettle(); await tester.tap(find.text('Report comment')); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('report-submit'))); await tester.pumpAndSettle(); expect(interactions.reports.single.targetType, InteractionTargetType.comment); expect(interactions.reports.single.targetId, '10'); expect(interactions.reports.single.reasonCode, 'SPAM'); expect(find.text('Report submitted. Thank you.'), findsOneWidget); }); testWidgets('should_show_backend_message_when_comment_report_is_duplicated', ( tester, ) async { final repository = FakeCommentRepository( pages: [ CommentPage( comments: [commentFixture(id: '10', content: 'Report me')], ), ], ); final interactions = FakeInteractionRepository() ..reportError = const ApiException(200003, '你已举报过该内容,我们正在处理'); await _pumpSheet( tester, repository: repository, interactions: interactions, ); await tester.tap(find.byKey(const Key('comment-menu-10'))); await tester.pumpAndSettle(); await tester.tap(find.text('Report comment')); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('report-submit'))); await tester.pumpAndSettle(); expect(find.text('你已举报过该内容,我们正在处理'), findsOneWidget); }); testWidgets('should_open_auth_gate_without_reporting_when_signed_out', ( tester, ) async { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); final repository = FakeCommentRepository( pages: [ CommentPage( comments: [commentFixture(id: '10', content: 'Report me')], ), ], ); final interactions = FakeInteractionRepository(); await _pumpSheet( tester, repository: repository, interactions: interactions, ); await tester.tap(find.byKey(const Key('comment-menu-10'))); await tester.pumpAndSettle(); await tester.tap(find.text('Report comment')); await tester.pumpAndSettle(); expect(find.text('Sign in to continue'), findsOneWidget); expect(interactions.reports, isEmpty); }); testWidgets('should_show_backend_message_when_reporting_own_comment', ( tester, ) async { final repository = FakeCommentRepository( pages: [ CommentPage( comments: [ commentFixture( id: '10', content: 'My comment', mine: true, ), ], ), ], ); final interactions = FakeInteractionRepository() ..reportError = const ApiException(400, '不能举报自己发布的内容'); await _pumpSheet( tester, repository: repository, interactions: interactions, ); await tester.tap(find.byKey(const Key('comment-menu-10'))); await tester.pumpAndSettle(); expect(find.text('Delete comment'), findsOneWidget); expect(find.text('Report comment'), findsOneWidget); await tester.tap(find.text('Report comment')); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('report-submit'))); await tester.pumpAndSettle(); expect(find.text('不能举报自己发布的内容'), findsOneWidget); }); } Future _pumpSheet( WidgetTester tester, { required FakeCommentRepository repository, FakeInteractionRepository? interactions, }) async { final auth = await _auth(); await tester.pumpWidget( MultiProvider( providers: [ Provider.value(value: repository), Provider.value( value: interactions ?? FakeInteractionRepository(), ), ChangeNotifierProvider.value(value: auth), ], child: MaterialApp( locale: const Locale('en'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const Scaffold( backgroundColor: Color(0xFF232328), body: DramaCommentsSheet(drama: _drama), ), ), ), ); await tester.pumpAndSettle(); } Future _auth() async { final client = ApiClient( baseUrl: 'http://127.0.0.1:8081', adapter: FakeHttpAdapter( (_) async => FakeHttpResponse('{"code":0,"message":"ok","data":null}'), ), ); final auth = AuthController(AuthApi(client), AuthSessionStore()); await auth.load(); return auth; }