376 lines
12 KiB
Dart
376 lines
12 KiB
Dart
// 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/core/platform/haptics.dart';
|
|
import 'package:fruitshorts/core/theme/app_colors.dart';
|
|
import 'package:fruitshorts/core/widgets/empty_state.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_haptics.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_show_compact_empty_state_when_no_comments_exist', (
|
|
tester,
|
|
) async {
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
expect(find.byKey(const Key('comment-list-empty')), findsOneWidget);
|
|
expect(find.byType(EmptyState), findsOneWidget);
|
|
expect(tester.widget<EmptyState>(find.byType(EmptyState)).compact, isTrue);
|
|
expect(find.text('No comments yet.'), findsOneWidget);
|
|
});
|
|
|
|
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<TextField>(
|
|
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,
|
|
),
|
|
);
|
|
final haptics = FakeHaptics();
|
|
|
|
await _pumpSheet(tester, repository: repository, haptics: haptics);
|
|
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);
|
|
expect(haptics.lightImpacts, 1);
|
|
final field = tester.widget<TextField>(
|
|
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);
|
|
final haptics = FakeHaptics();
|
|
|
|
await _pumpSheet(
|
|
tester,
|
|
repository: repository,
|
|
interactions: interactions,
|
|
haptics: haptics,
|
|
);
|
|
await tester.tap(find.byKey(const Key('comment-like-10')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(interactions.likeTargets, ['COMMENT:10']);
|
|
expect(haptics.lightImpacts, 1);
|
|
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();
|
|
final haptics = FakeHaptics();
|
|
|
|
await _pumpSheet(
|
|
tester,
|
|
repository: repository,
|
|
interactions: interactions,
|
|
haptics: haptics,
|
|
);
|
|
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(haptics.lightImpacts, 1);
|
|
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<void> _pumpSheet(
|
|
WidgetTester tester, {
|
|
required FakeCommentRepository repository,
|
|
FakeInteractionRepository? interactions,
|
|
FakeHaptics? haptics,
|
|
}) async {
|
|
final auth = await _auth();
|
|
await tester.pumpWidget(
|
|
MultiProvider(
|
|
providers: [
|
|
Provider<CommentRepository>.value(value: repository),
|
|
Provider<InteractionRepository>.value(
|
|
value: interactions ?? FakeInteractionRepository(),
|
|
),
|
|
ChangeNotifierProvider<AuthController>.value(value: auth),
|
|
Provider<Haptics>.value(value: haptics ?? FakeHaptics()),
|
|
],
|
|
child: MaterialApp(
|
|
locale: const Locale('en'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
home: const Scaffold(
|
|
backgroundColor: AppColors.ink,
|
|
body: DramaCommentsSheet(drama: _drama),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
Future<AuthController> _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;
|
|
}
|