feat(flutter): 接入互动评论点赞举报

This commit is contained in:
Zhou Weidong committed 2026-10-17 15:26:42 +08:00
1 parent 45f5f25640
commit d361034226
39 files changed
+3298 -462

No files matched your search

@@ -0,0 +1,67 @@
// Author: fruit.com.cn
import 'dart:convert';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/interaction.dart';
import 'package:fruitshorts/core/data/remote/api_client.dart';
import 'package:fruitshorts/core/data/remote/interaction_api.dart';
import '../support/fake_http_adapter.dart';
void main() {
String envelope(Object? data) =>
jsonEncode({'code': 0, 'message': 'ok', 'data': data});
test('should_send_like_target_and_parse_string_count_when_toggled', () async {
final adapter = FakeHttpAdapter(
(_) async => FakeHttpResponse(
envelope({'liked': true, 'count': '2111012753262120962'}),
),
);
final api = InteractionApi(
ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter),
);
final result = await api.toggleLike(
targetType: InteractionTargetType.drama,
targetId: '5',
);
expect(adapter.requests.single.method, 'POST');
expect(adapter.requests.single.uri.path, '/app/likes/toggle');
expect(adapter.requests.single.data, {
'targetType': 'DRAMA',
'targetId': '5',
});
expect(result.liked, isTrue);
expect(result.count, 2111012753262120962);
});
test('should_submit_fixed_reason_code_and_optional_description_when_reporting', () async {
final adapter = FakeHttpAdapter(
(_) async => FakeHttpResponse(envelope('2111012753262120999')),
);
final api = InteractionApi(
ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter),
);
final reportId = await api.report(
const ReportRequest(
targetType: InteractionTargetType.comment,
targetId: '7',
reasonCode: 'SPAM',
description: 'Repeated advertising',
),
);
expect(adapter.requests.single.uri.path, '/app/reports');
expect(adapter.requests.single.data, {
'targetType': 'COMMENT',
'targetId': '7',
'reasonCode': 'SPAM',
'description': 'Repeated advertising',
});
expect(reportId, '2111012753262120999');
});
}
@@ -0,0 +1,60 @@
// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/interaction.dart';
import 'package:fruitshorts/core/data/remote/api_client.dart';
import 'package:fruitshorts/core/data/services/interaction_controller.dart';
import '../support/fake_interaction_repositories.dart';
void main() {
test('should_update_drama_like_optimistically_and_use_server_state_when_toggle_succeeds', () async {
final repository = FakeInteractionRepository();
final gate = Completer<void>();
repository.likeGate = gate;
final controller = InteractionController(repository);
final future = controller.toggleDramaLike('5');
expect(controller.isLiked('5'), isTrue);
expect(repository.likeTargets, ['DRAMA:5']);
repository.nextLikeResult = const LikeToggleResult(liked: false, count: 0);
gate.complete();
final result = await future;
expect(result.isSuccess, isTrue);
expect(controller.isLiked('5'), isFalse);
controller.dispose();
});
test('should_rollback_drama_like_when_toggle_fails', () async {
final repository = FakeInteractionRepository(
nextLikeResult: const LikeToggleResult(liked: true, count: 3),
);
final controller = InteractionController(repository);
await controller.toggleDramaLike('5');
repository.likeError = const ApiException(500, '点赞失败');
final result = await controller.toggleDramaLike('5');
expect(result.isSuccess, isFalse);
expect(result.errorMessage, '点赞失败');
expect(controller.isLiked('5'), isTrue);
controller.dispose();
});
test('should_clear_account_scoped_drama_likes_when_session_changes', () async {
final repository = FakeInteractionRepository(
nextLikeResult: const LikeToggleResult(liked: true, count: 1),
);
final controller = InteractionController(repository);
await controller.toggleDramaLike('5');
controller.clear();
expect(controller.isLiked('5'), isFalse);
controller.dispose();
});
}
+55
View File
@@ -0,0 +1,55 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/interaction.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/interaction/widgets/report_dialog.dart';
void main() {
testWidgets('should_return_selected_reason_and_description_when_submitted', (
tester,
) async {
ReportDialogResult? result;
await tester.pumpWidget(
MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Builder(
builder: (context) => Scaffold(
body: TextButton(
key: const Key('open-report-dialog'),
onPressed: () async {
result = await showReportDialog(
context,
title: 'Report comment',
);
},
child: const Text('Open'),
),
),
),
),
);
await tester.tap(find.byKey(const Key('open-report-dialog')));
await tester.pumpAndSettle();
expect(find.text('Spam'), findsOneWidget);
expect(find.text('Harassment'), findsOneWidget);
expect(find.text('Sexual content'), findsOneWidget);
expect(find.text('Advertising'), findsOneWidget);
expect(find.text('Other'), findsOneWidget);
await tester.tap(find.byKey(const Key('report-reason-AD')));
await tester.enterText(
find.byKey(const Key('report-description')),
'Repeat promotion',
);
await tester.tap(find.byKey(const Key('report-submit')));
await tester.pumpAndSettle();
expect(result?.reason, ReportReason.ad);
expect(result?.description, 'Repeat promotion');
});
}