760 lines
25 KiB
Dart
760 lines
25 KiB
Dart
// Author: fruit.com.cn
|
|
|
|
import 'dart:convert';
|
|
|
|
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/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_gate_guest_comment_input_without_focus', (tester) async {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.empty();
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
final input = find.byKey(const Key('comment-input'));
|
|
final field = tester.widget<TextField>(input);
|
|
|
|
expect(field.decoration?.hintText, 'Sign in to comment');
|
|
expect(field.focusNode?.hasFocus, isFalse);
|
|
await tester.tap(input);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Sign in to continue'), findsOneWidget);
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isFalse);
|
|
await tester.tap(find.text('Not now'));
|
|
await tester.pumpAndSettle();
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('should_focus_comment_input_after_successful_login', (
|
|
tester,
|
|
) async {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.empty();
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
final auth = await _pumpSheet(tester, repository: repository);
|
|
|
|
await tester.tap(find.byKey(const Key('comment-input')));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
await tester.tap(find.text('Sign In'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
(auth as _TestAuthController).setSignedIn(true);
|
|
await tester.pump();
|
|
Navigator.of(tester.element(find.byKey(const Key('auth-landing')))).pop();
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
expect(
|
|
tester
|
|
.widget<TextField>(find.byKey(const Key('comment-input')))
|
|
.focusNode
|
|
?.hasFocus,
|
|
isTrue,
|
|
);
|
|
});
|
|
|
|
testWidgets('should_gate_guest_post_without_creating_comment', (
|
|
tester,
|
|
) async {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.empty();
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
await tester.tap(find.byKey(const Key('comment-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Sign in to continue'), findsOneWidget);
|
|
expect(repository.publishCalls, isEmpty);
|
|
});
|
|
|
|
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);
|
|
expect(
|
|
tester.widget<Text>(find.byKey(const Key('comment-count-label'))).data,
|
|
'Comments',
|
|
);
|
|
|
|
await tester.tap(find.byKey(const Key('comment-list-load-more')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Second page'), findsOneWidget);
|
|
expect(find.text('2 comments'), findsOneWidget);
|
|
expect(repository.pageCalls.last.cursor, '10');
|
|
});
|
|
|
|
testWidgets('should_show_server_total_that_includes_replies', (tester) async {
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [
|
|
commentFixture(id: '10', content: 'Root one'),
|
|
commentFixture(id: '11', content: 'Root two'),
|
|
],
|
|
),
|
|
],
|
|
countResult: 9,
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
expect(find.text('9 comments'), findsOneWidget);
|
|
expect(repository.countCalls, ['5']);
|
|
});
|
|
|
|
testWidgets('should_fall_back_to_exact_count_when_server_total_fails', (
|
|
tester,
|
|
) async {
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [
|
|
commentFixture(id: '10', content: 'Root one'),
|
|
commentFixture(id: '11', content: 'Root two'),
|
|
],
|
|
),
|
|
],
|
|
countError: StateError('offline'),
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
expect(find.text('2 comments'), findsOneWidget);
|
|
});
|
|
|
|
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');
|
|
// 发送失败时保留输入与焦点,用户可以直接改完再发。
|
|
expect(field.focusNode?.hasFocus, isTrue);
|
|
},
|
|
);
|
|
|
|
testWidgets('should_clear_and_keep_input_focused_after_successful_post', (
|
|
tester,
|
|
) async {
|
|
addTearDown(tester.view.resetViewInsets);
|
|
final repository = FakeCommentRepository(
|
|
pages: [const CommentPage()],
|
|
publishResult: commentFixture(id: '11', content: 'Nice demo', mine: true),
|
|
countResult: 1,
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
final input = find.byKey(const Key('comment-input'));
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 180);
|
|
await tester.tap(input);
|
|
await tester.pumpAndSettle();
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isTrue);
|
|
await tester.enterText(input, 'Nice demo');
|
|
await tester.pump();
|
|
expect(
|
|
tester
|
|
.widget<TextButton>(find.byKey(const Key('comment-post')))
|
|
.onPressed,
|
|
isNotNull,
|
|
);
|
|
await tester.tap(find.byKey(const Key('comment-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.widget<TextField>(input).controller?.text, isEmpty);
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isTrue);
|
|
expect(tester.widget<TextField>(input).maxLines, 4);
|
|
expect(find.byKey(const Key('comment-list')), findsOneWidget);
|
|
expect(find.byKey(const Key('comment-count-label')), findsOneWidget);
|
|
expect(
|
|
tester
|
|
.widget<TextButton>(find.byKey(const Key('comment-post')))
|
|
.onPressed,
|
|
isNull,
|
|
);
|
|
expect(repository.countCalls, ['5', '5']);
|
|
});
|
|
|
|
testWidgets('should_keep_composer_separate_when_keyboard_opens', (
|
|
tester,
|
|
) async {
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
addTearDown(tester.view.resetViewInsets);
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [commentFixture(id: '10', content: 'A comment')],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
expect(find.byKey(const Key('comment-list')), findsOneWidget);
|
|
final editableBefore = tester.state<EditableTextState>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('comment-input')),
|
|
matching: find.byType(EditableText),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byKey(const Key('comment-input')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('comment-list')), findsOneWidget);
|
|
expect(find.byKey(const Key('comment-count-label')), findsOneWidget);
|
|
expect(find.byKey(const Key('comment-composer')), findsOneWidget);
|
|
expect(find.byKey(const Key('comment-input-surface')), findsOneWidget);
|
|
expect(
|
|
tester.widget<TextField>(find.byKey(const Key('comment-input'))).maxLines,
|
|
1,
|
|
);
|
|
expect(tester.getSize(find.byKey(const Key('comment-input'))).height, 44);
|
|
final editableAfter = tester.state<EditableTextState>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('comment-input')),
|
|
matching: find.byType(EditableText),
|
|
),
|
|
);
|
|
expect(identical(editableBefore, editableAfter), isTrue);
|
|
|
|
final sheet = find.byKey(const Key('player-comments-sheet'));
|
|
final list = find.byKey(const Key('comment-list'));
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 180);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
|
|
expect(sheet, findsOneWidget);
|
|
expect(list, findsOneWidget);
|
|
expect(find.byKey(const Key('comment-count-label')), findsOneWidget);
|
|
final field = tester.widget<TextField>(
|
|
find.byKey(const Key('comment-input')),
|
|
);
|
|
expect(field.minLines, 1);
|
|
expect(field.maxLines, 4);
|
|
expect(field.style?.fontSize, 15);
|
|
expect(
|
|
tester
|
|
.widget<TextButton>(find.byKey(const Key('comment-post')))
|
|
.onPressed,
|
|
isNull,
|
|
);
|
|
expect(tester.getSize(find.byKey(const Key('comment-post'))).height, 32);
|
|
final listRect = tester.getRect(list);
|
|
final inputRect = tester.getRect(find.byKey(const Key('comment-input')));
|
|
expect(listRect.overlaps(inputRect), isFalse);
|
|
expect(listRect.bottom, lessThanOrEqualTo(inputRect.top));
|
|
expect(find.byIcon(Icons.image_outlined), findsNothing);
|
|
expect(find.byIcon(Icons.emoji_emotions_outlined), findsNothing);
|
|
expect(find.byIcon(Icons.alternate_email_rounded), findsNothing);
|
|
|
|
await tester.enterText(find.byKey(const Key('comment-input')), 'Nice');
|
|
await tester.pump();
|
|
expect(
|
|
tester
|
|
.widget<TextButton>(find.byKey(const Key('comment-post')))
|
|
.onPressed,
|
|
isNotNull,
|
|
);
|
|
|
|
final editableDuringKeyboard = tester.state<EditableTextState>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('comment-input')),
|
|
matching: find.byType(EditableText),
|
|
),
|
|
);
|
|
expect(identical(editableAfter, editableDuringKeyboard), isTrue);
|
|
expect(tester.getBottomLeft(sheet).dy, closeTo(844 - 180, 0.1));
|
|
expect(
|
|
tester.widget<ListView>(list).padding?.resolve(TextDirection.ltr).bottom,
|
|
greaterThanOrEqualTo(180 + 44),
|
|
);
|
|
expect(
|
|
tester.getBottomLeft(find.byKey(const Key('comment-input'))).dy,
|
|
lessThanOrEqualTo(844 - 180),
|
|
);
|
|
expect(
|
|
tester
|
|
.widget<TextField>(find.byKey(const Key('comment-input')))
|
|
.focusNode
|
|
?.hasFocus,
|
|
isTrue,
|
|
);
|
|
|
|
tester.view.viewInsets = const FakeViewPadding();
|
|
await tester.pumpAndSettle();
|
|
expect(list, findsOneWidget);
|
|
expect(find.byKey(const Key('comment-count-label')), findsOneWidget);
|
|
expect(find.byKey(const Key('comment-composer')), findsOneWidget);
|
|
final collapsedField = tester.widget<TextField>(
|
|
find.byKey(const Key('comment-input')),
|
|
);
|
|
expect(collapsedField.maxLines, 1);
|
|
expect(tester.getSize(find.byKey(const Key('comment-input'))).height, 44);
|
|
expect(tester.getSize(sheet).height, lessThan(520));
|
|
final editableAfterKeyboard = tester.state<EditableTextState>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('comment-input')),
|
|
matching: find.byType(EditableText),
|
|
),
|
|
);
|
|
expect(identical(editableAfter, editableAfterKeyboard), isTrue);
|
|
expect(
|
|
tester
|
|
.widget<TextField>(find.byKey(const Key('comment-input')))
|
|
.controller
|
|
?.text,
|
|
'Nice',
|
|
);
|
|
expect(
|
|
tester
|
|
.widget<TextField>(find.byKey(const Key('comment-input')))
|
|
.focusNode
|
|
?.hasFocus,
|
|
isTrue,
|
|
);
|
|
});
|
|
|
|
testWidgets('should_use_polished_comment_sizes', (tester) async {
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [commentFixture(id: '10', content: 'Great episode')],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('comment-post'))),
|
|
const Size(38, 38),
|
|
);
|
|
expect(tester.getSize(find.byKey(const Key('comment-input'))).height, 44);
|
|
expect(find.byKey(const Key('player-comments-drag-handle')), findsNothing);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('player-comments-close'))),
|
|
const Size(44, 44),
|
|
);
|
|
expect(
|
|
find.descendant(
|
|
of: find.byKey(const Key('player-comments-close')),
|
|
matching: find.byIcon(Icons.keyboard_arrow_down_rounded),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
final title = tester.widget<Text>(
|
|
find.byKey(const Key('comment-count-label')),
|
|
);
|
|
expect(title.style?.fontSize, 16);
|
|
expect(title.style?.fontWeight, FontWeight.w600);
|
|
expect(
|
|
tester.getCenter(find.byKey(const Key('comment-count-label'))).dx,
|
|
closeTo(
|
|
tester.getCenter(find.byKey(const Key('player-comments-sheet'))).dx,
|
|
0.5,
|
|
),
|
|
);
|
|
final avatar = tester.widget<CircleAvatar>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('comment-item-10')),
|
|
matching: find.byType(CircleAvatar),
|
|
),
|
|
);
|
|
expect(avatar.radius, 20);
|
|
final body = tester.widget<Text>(find.text('Great episode'));
|
|
expect(body.style?.fontSize, 16);
|
|
expect(body.style?.height, 1.4);
|
|
final composer = tester.widget<Container>(
|
|
find.byKey(const Key('comment-composer')),
|
|
);
|
|
final decoration = composer.decoration! as BoxDecoration;
|
|
expect(decoration.color, AppColors.commentInput);
|
|
expect(decoration.borderRadius, BorderRadius.circular(22));
|
|
});
|
|
|
|
testWidgets('should_hide_report_and_delete_actions', (tester) async {
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [
|
|
commentFixture(id: '10', content: 'My comment', mine: true),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
expect(
|
|
find.byWidgetPredicate(
|
|
(widget) =>
|
|
widget.key is ValueKey<String> &&
|
|
(widget.key! as ValueKey<String>).value.startsWith('comment-menu-'),
|
|
),
|
|
findsNothing,
|
|
);
|
|
expect(find.text('Delete comment'), findsNothing);
|
|
expect(find.text('Report comment'), findsNothing);
|
|
expect(find.byIcon(Icons.more_horiz_rounded), findsNothing);
|
|
});
|
|
|
|
testWidgets('should_size_short_list_to_content', (tester) async {
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [
|
|
commentFixture(id: '10', content: 'Short list one'),
|
|
commentFixture(id: '11', content: 'Short list two'),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
final sheet = find.byKey(const Key('player-comments-sheet'));
|
|
expect(tester.getSize(sheet).height, lessThan(520));
|
|
final lastCommentBottom = tester
|
|
.getBottomLeft(find.byKey(const Key('comment-item-11')))
|
|
.dy;
|
|
final composerTop = tester
|
|
.getTopLeft(find.byKey(const Key('comment-composer')))
|
|
.dy;
|
|
expect(composerTop - lastCommentBottom, lessThan(80));
|
|
});
|
|
|
|
testWidgets('should_keep_minimum_height_for_empty_state', (tester) async {
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('player-comments-sheet'))).height,
|
|
greaterThanOrEqualTo(220),
|
|
);
|
|
});
|
|
|
|
testWidgets('should_keep_long_comment_list_scrollable', (tester) async {
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final repository = FakeCommentRepository(
|
|
pages: [CommentPage(comments: _comments(30))],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
final scrollable = find
|
|
.descendant(
|
|
of: find.byKey(const Key('comment-list')),
|
|
matching: find.byType(Scrollable),
|
|
)
|
|
.first;
|
|
final position = tester.state<ScrollableState>(scrollable).position;
|
|
expect(position.maxScrollExtent, greaterThan(0));
|
|
|
|
await tester.drag(
|
|
find.byKey(const Key('comment-list')),
|
|
const Offset(0, -300),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(position.pixels, greaterThan(0));
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('player-comments-sheet'))).height,
|
|
520,
|
|
);
|
|
});
|
|
|
|
testWidgets('should_hide_comment_like_controls', (tester) async {
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [commentFixture(id: '10', content: 'No likes shown')],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
expect(
|
|
find.byWidgetPredicate(
|
|
(widget) =>
|
|
widget.key is ValueKey<String> &&
|
|
(widget.key! as ValueKey<String>).value.startsWith('comment-like-'),
|
|
),
|
|
findsNothing,
|
|
);
|
|
expect(find.byIcon(Icons.favorite_rounded), findsNothing);
|
|
expect(find.byIcon(Icons.favorite_border_rounded), findsNothing);
|
|
});
|
|
|
|
testWidgets('should_close_sheet_from_down_arrow', (tester) async {
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
await tester.tap(find.byKey(const Key('player-comments-close')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('player-comments-sheet')), findsNothing);
|
|
});
|
|
|
|
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();
|
|
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
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.pump();
|
|
await tester.tap(find.byKey(const Key('comment-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(repository.publishCalls.single.parentId, '10');
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
await tester.pumpAndSettle();
|
|
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);
|
|
});
|
|
}
|
|
|
|
Future<AuthController> _pumpSheet(
|
|
WidgetTester tester, {
|
|
required FakeCommentRepository repository,
|
|
FakeHaptics? haptics,
|
|
}) async {
|
|
final auth = await _auth();
|
|
await tester.pumpWidget(
|
|
MultiProvider(
|
|
providers: [
|
|
Provider<CommentRepository>.value(value: repository),
|
|
Provider<InteractionRepository>.value(
|
|
value: FakeInteractionRepository(),
|
|
),
|
|
ChangeNotifierProvider<AuthController>.value(value: auth),
|
|
Provider<Haptics>.value(value: haptics ?? FakeHaptics()),
|
|
],
|
|
child: MaterialApp(
|
|
locale: const Locale('en'),
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
initialRoute: '/comments',
|
|
routes: {
|
|
'/': (_) => const Scaffold(backgroundColor: AppColors.ink),
|
|
'/comments': (_) => const Scaffold(
|
|
backgroundColor: AppColors.ink,
|
|
resizeToAvoidBottomInset: false,
|
|
body: Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: DramaCommentsSheet(drama: _drama),
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
return auth;
|
|
}
|
|
|
|
Future<AuthController> _auth() async {
|
|
final client = ApiClient(
|
|
baseUrl: 'http://127.0.0.1:8081',
|
|
adapter: FakeHttpAdapter((options) async {
|
|
if (options.uri.path == '/app/auth/login') {
|
|
return FakeHttpResponse(
|
|
jsonEncode({
|
|
'code': 0,
|
|
'message': 'ok',
|
|
'data': {
|
|
'accessToken': 'access',
|
|
'refreshToken': 'refresh',
|
|
'expiresIn': 7200,
|
|
'user': {'uid': '9', 'nickname': 'Viewer', 'avatarUrl': ''},
|
|
},
|
|
}),
|
|
);
|
|
}
|
|
return FakeHttpResponse('{"code":0,"message":"ok","data":null}');
|
|
}),
|
|
);
|
|
final store = AuthSessionStore();
|
|
final session = await store.read();
|
|
final auth = _TestAuthController(
|
|
AuthApi(client),
|
|
store,
|
|
signedIn: session != null,
|
|
);
|
|
await auth.load();
|
|
return auth;
|
|
}
|
|
|
|
List<DramaComment> _comments(int count) => [
|
|
for (var index = 0; index < count; index++)
|
|
commentFixture(id: '${index + 10}', content: 'Comment ${index + 1}'),
|
|
];
|
|
|
|
class _TestAuthController extends AuthController {
|
|
_TestAuthController(super.api, super.store, {required this._signedIn});
|
|
|
|
bool _signedIn;
|
|
|
|
@override
|
|
bool get isSignedIn => _signedIn;
|
|
|
|
void setSignedIn(bool value) {
|
|
_signedIn = value;
|
|
notifyListeners();
|
|
}
|
|
}
|