1143 lines
40 KiB
Dart
1143 lines
40 KiB
Dart
// Author: fruit.com.cn
|
|
|
|
import 'dart:convert';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.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() {
|
|
testWidgets('should_ignore_stale_keyboard_inset_when_composer_opens', (
|
|
tester,
|
|
) async {
|
|
const keyboardChannel = MethodChannel('com.stormg.lumadrama/keyboard');
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
keyboardChannel,
|
|
(call) async => call.method == 'get' ? 300 : null,
|
|
);
|
|
addTearDown(() {
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
keyboardChannel,
|
|
null,
|
|
);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final composer = find.byKey(const Key('comment-composer-sheet'));
|
|
final panel = find.byKey(const Key('comment-composer-panel'));
|
|
final input = find.byKey(const Key('comment-composer-input'));
|
|
expect(
|
|
find.byKey(const Key('comment-composer-focus-scope')),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.getSize(composer).height, inInclusiveRange(100, 150));
|
|
expect(
|
|
tester.getSize(panel).height,
|
|
closeTo(tester.getSize(composer).height, 1),
|
|
);
|
|
expect(tester.getBottomLeft(panel).dy, closeTo(844, 0.1));
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isTrue);
|
|
expect(tester.testTextInput.isVisible, isTrue);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
setUp(() {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.withData({
|
|
'auth.accessToken': 'test-access',
|
|
'auth.refreshToken': 'test-refresh',
|
|
'auth.user': '{"uid":"9","nickname":"Me","avatarUrl":""}',
|
|
});
|
|
});
|
|
|
|
testWidgets('should_follow_live_view_insets_on_ios', (tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
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: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 300);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 160));
|
|
|
|
final animatedPadding = tester.widget<AnimatedPadding>(
|
|
find.byKey(const Key('comment-composer-keyboard-padding')),
|
|
);
|
|
expect((animatedPadding.padding as EdgeInsets).bottom, 300);
|
|
expect(
|
|
tester.getBottomLeft(find.byKey(const Key('comment-composer-post'))).dy,
|
|
lessThanOrEqualTo(844 - 300),
|
|
);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets('should_use_zero_live_inset_when_ios_keyboard_is_hidden', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final animatedPadding = tester.widget<AnimatedPadding>(
|
|
find.byKey(const Key('comment-composer-keyboard-padding')),
|
|
);
|
|
expect((animatedPadding.padding as EdgeInsets).bottom, 0);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets('should_open_guest_comment_composer_without_login', (
|
|
tester,
|
|
) async {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.empty();
|
|
final repository = FakeCommentRepository(pages: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
final placeholder = find.byKey(const Key('comment-input-placeholder'));
|
|
|
|
expect(placeholder, findsOneWidget);
|
|
await tester.tap(placeholder);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('comment-composer-sheet')), findsOneWidget);
|
|
expect(find.byKey(const Key('auth-landing')), findsNothing);
|
|
});
|
|
|
|
testWidgets('should_focus_guest_comment_input_immediately', (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-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
tester
|
|
.widget<TextField>(find.byKey(const Key('comment-composer-input')))
|
|
.focusNode
|
|
?.hasFocus,
|
|
isTrue,
|
|
);
|
|
});
|
|
|
|
testWidgets('should_open_composer_sheet_when_fake_send_is_tapped', (
|
|
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: [const CommentPage()]);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
await tester.tap(find.byKey(const Key('comment-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final composer = find.byKey(const Key('comment-composer-sheet'));
|
|
expect(composer, findsOneWidget);
|
|
final compactHeight = tester.getSize(composer).height;
|
|
expect(compactHeight, inInclusiveRange(100, 150));
|
|
final input = find.byKey(const Key('comment-composer-input'));
|
|
expect(input, findsOneWidget);
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isTrue);
|
|
expect(
|
|
tester.widget<TextField>(input).textAlignVertical,
|
|
TextAlignVertical.center,
|
|
);
|
|
|
|
await tester.enterText(input, 'First line\nSecond line');
|
|
await tester.pump();
|
|
expect(tester.getSize(composer).height, greaterThan(compactHeight));
|
|
expect(tester.getSize(composer).height, lessThan(844 / 2));
|
|
final multilineField = tester.widget<TextField>(input);
|
|
expect(multilineField.textAlignVertical, TextAlignVertical.top);
|
|
expect(multilineField.style?.height, 1.4);
|
|
});
|
|
|
|
testWidgets('should_publish_guest_comment_without_login', (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();
|
|
|
|
await tester.enterText(
|
|
find.byKey(const Key('comment-composer-input')),
|
|
'Guest comment',
|
|
);
|
|
await tester.pump();
|
|
await tester.tap(find.byKey(const Key('comment-composer-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('auth-landing')), findsNothing);
|
|
expect(repository.publishCalls, hasLength(1));
|
|
expect(repository.publishCalls.single.content, 'Guest comment');
|
|
expect(find.text('Guest comment'), findsOneWidget);
|
|
});
|
|
|
|
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_scope_list_count_and_publish_to_episode', (tester) async {
|
|
final repository = FakeCommentRepository(
|
|
pages: [const CommentPage()],
|
|
countResult: 3,
|
|
publishResult: commentFixture(
|
|
id: '11',
|
|
content: 'Episode comment',
|
|
mine: true,
|
|
),
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository, episodeId: 'episode-7');
|
|
|
|
expect(repository.pageCalls.single.episodeId, 'episode-7');
|
|
expect(repository.countEpisodeCalls, ['episode-7']);
|
|
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(
|
|
find.byKey(const Key('comment-composer-input')),
|
|
'Episode comment',
|
|
);
|
|
await tester.pump();
|
|
await tester.tap(find.byKey(const Key('comment-composer-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(repository.publishCalls.single.episodeId, 'episode-7');
|
|
expect(repository.countEpisodeCalls, ['episode-7', 'episode-7']);
|
|
});
|
|
|
|
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,
|
|
locale: const Locale('zh'),
|
|
);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(
|
|
find.byKey(const Key('comment-composer-input')),
|
|
'blocked text',
|
|
);
|
|
await tester.pump();
|
|
await tester.tap(find.byKey(const Key('comment-composer-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('评论包含敏感词'), findsOneWidget);
|
|
final field = tester.widget<TextField>(
|
|
find.byKey(const Key('comment-composer-input')),
|
|
);
|
|
expect(field.controller?.text, 'blocked text');
|
|
expect(find.text('发表成功'), findsNothing);
|
|
// 发送失败时保留输入与焦点,用户可以直接改完再发。
|
|
expect(field.focusNode?.hasFocus, isTrue);
|
|
},
|
|
);
|
|
|
|
testWidgets('should_close_composer_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,
|
|
locale: const Locale('zh'),
|
|
);
|
|
final input = find.byKey(const Key('comment-composer-input'));
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 180);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
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-composer-post')))
|
|
.onPressed,
|
|
isNotNull,
|
|
);
|
|
await tester.tap(find.byKey(const Key('comment-composer-post')));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(find.byKey(const Key('comment-composer-sheet')), findsNothing);
|
|
final successOverlay = find.byKey(
|
|
const Key('comment-post-success-snackbar'),
|
|
);
|
|
expect(successOverlay, findsOneWidget);
|
|
expect(find.text('发表成功'), findsOneWidget);
|
|
expect(find.byType(SnackBar), findsNothing);
|
|
final capsule = tester.widget<Container>(successOverlay);
|
|
final decoration = capsule.decoration! as BoxDecoration;
|
|
expect(decoration.color, Colors.black.withValues(alpha: 0.82));
|
|
expect(decoration.borderRadius, BorderRadius.circular(8));
|
|
expect(
|
|
capsule.padding,
|
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
);
|
|
final successText = tester.widget<Text>(find.text('发表成功'));
|
|
expect(successText.style?.color, Colors.white);
|
|
expect(successText.style?.fontSize, 14);
|
|
expect(successText.style?.decoration, TextDecoration.none);
|
|
final screen = tester.view.physicalSize / tester.view.devicePixelRatio;
|
|
final toast = tester.getRect(successOverlay);
|
|
expect(toast.center.dy, lessThan(screen.height * 0.4));
|
|
expect(toast.center.dy, greaterThan(screen.height * 0.2));
|
|
final placeholderRect = tester.getRect(
|
|
find.byKey(const Key('comment-input-placeholder')),
|
|
);
|
|
expect(
|
|
tester.getRect(successOverlay).bottom,
|
|
lessThan(placeholderRect.top),
|
|
);
|
|
await tester.pump(const Duration(milliseconds: 1200));
|
|
expect(successOverlay, findsNothing);
|
|
expect(find.byKey(const Key('comment-list')), findsOneWidget);
|
|
expect(find.byKey(const Key('comment-count-label')), findsOneWidget);
|
|
expect(find.byKey(const Key('comment-input-placeholder')), findsOneWidget);
|
|
expect(repository.countCalls, ['5', '5']);
|
|
});
|
|
|
|
testWidgets('should_keep_comment_panel_fixed_and_open_composer_sheet', (
|
|
tester,
|
|
) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
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);
|
|
final commentsSheet = find.byKey(const Key('player-comments-sheet'));
|
|
final list = find.byKey(const Key('comment-list'));
|
|
final placeholder = find.byKey(const Key('comment-input-placeholder'));
|
|
final sheetRect = tester.getRect(commentsSheet);
|
|
final sheetHeight = tester.getSize(commentsSheet).height;
|
|
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final composer = find.byKey(const Key('comment-composer-sheet'));
|
|
final composerPanel = find.byKey(const Key('comment-composer-panel'));
|
|
final composerInput = find.byKey(const Key('comment-composer-input'));
|
|
expect(composer, findsOneWidget);
|
|
expect(composerPanel, findsOneWidget);
|
|
final overlayRoot = find.byKey(const Key('comment-composer-overlay'));
|
|
expect(overlayRoot, findsOneWidget);
|
|
final overlayStack = tester.widget<Stack>(overlayRoot);
|
|
expect(overlayStack.children, hasLength(2));
|
|
final dismissLayer = overlayStack.children.first as Positioned;
|
|
expect(dismissLayer.left, 0);
|
|
expect(dismissLayer.top, 0);
|
|
expect(dismissLayer.right, 0);
|
|
expect(dismissLayer.bottom, 0);
|
|
final dismissGesture = dismissLayer.child as GestureDetector;
|
|
expect(dismissGesture.child, isNull);
|
|
expect(dismissGesture.behavior, HitTestBehavior.opaque);
|
|
final compactHeight = tester.getSize(composer).height;
|
|
expect(compactHeight, inInclusiveRange(100, 150));
|
|
expect(tester.widget<TextField>(composerInput).focusNode?.hasFocus, isTrue);
|
|
final underlyingSheet = find.byKey(
|
|
const Key('player-comments-sheet'),
|
|
skipOffstage: false,
|
|
);
|
|
expect(tester.getRect(underlyingSheet), sheetRect);
|
|
expect(tester.getSize(underlyingSheet).height, closeTo(sheetHeight, 0.1));
|
|
|
|
final field = tester.widget<TextField>(composerInput);
|
|
expect(field.minLines, 1);
|
|
expect(field.maxLines, 4);
|
|
expect(field.style?.fontSize, 15);
|
|
expect(find.byIcon(Icons.image_outlined), findsNothing);
|
|
expect(find.byIcon(Icons.emoji_emotions_outlined), findsNothing);
|
|
expect(find.byIcon(Icons.alternate_email_rounded), findsNothing);
|
|
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 180);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 90));
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
final keyboardUpInputTop = tester.getTopLeft(composer).dy;
|
|
final keyboardUpBottom = tester.getBottomLeft(composer).dy;
|
|
final keyboardUpPanelRect = tester.getRect(composerPanel);
|
|
expect(tester.getSize(composer).height, closeTo(compactHeight, 0.1));
|
|
expect(keyboardUpBottom, closeTo(844 - 180, 0.1));
|
|
expect(keyboardUpPanelRect.bottom, closeTo(844, 0.1));
|
|
expect(
|
|
tester.getBottomLeft(composerInput).dy,
|
|
lessThanOrEqualTo(844 - 180),
|
|
);
|
|
expect(tester.getRect(underlyingSheet), sheetRect);
|
|
|
|
tester.view.viewInsets = const FakeViewPadding();
|
|
await tester.pumpAndSettle();
|
|
expect(composer, findsOneWidget);
|
|
expect(tester.getTopLeft(composer).dy, closeTo(keyboardUpInputTop, 0.1));
|
|
expect(tester.getBottomLeft(composer).dy, closeTo(keyboardUpBottom, 0.1));
|
|
expect(tester.getRect(composerPanel), keyboardUpPanelRect);
|
|
expect(tester.widget<Material>(composerPanel).color, AppColors.card);
|
|
expect(tester.getBottomLeft(composerPanel).dy, closeTo(844, 0.1));
|
|
expect(commentsSheet, findsOneWidget);
|
|
expect(list, findsOneWidget);
|
|
expect(placeholder, findsOneWidget);
|
|
expect(tester.getRect(commentsSheet), sheetRect);
|
|
expect(tester.getSize(commentsSheet).height, closeTo(sheetHeight, 0.1));
|
|
|
|
await tester.tapAt(const Offset(20, 20));
|
|
await tester.pumpAndSettle();
|
|
expect(composer, findsNothing);
|
|
|
|
await tester.tap(placeholder);
|
|
await tester.pumpAndSettle();
|
|
expect(composer, findsOneWidget);
|
|
expect(tester.getBottomLeft(composer).dy, closeTo(844, 0.1));
|
|
debugDefaultTargetPlatformOverride = null;
|
|
});
|
|
|
|
testWidgets(
|
|
'should_keep_composer_input_position_when_safe_area_padding_changes',
|
|
(tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
tester.view.padding = const FakeViewPadding();
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
addTearDown(tester.view.resetViewInsets);
|
|
addTearDown(tester.view.resetPadding);
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [commentFixture(id: '10', content: 'A comment')],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final input = find.byKey(const Key('comment-composer-input'));
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 180);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 90));
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
final keyboardUpInputRect = tester.getRect(input);
|
|
|
|
tester.view.viewInsets = const FakeViewPadding();
|
|
tester.view.padding = const FakeViewPadding(bottom: 48);
|
|
await tester.pumpAndSettle();
|
|
|
|
final keyboardClosedInputRect = tester.getRect(input);
|
|
expect(
|
|
keyboardClosedInputRect.top,
|
|
closeTo(keyboardUpInputRect.top, 0.1),
|
|
);
|
|
expect(
|
|
keyboardClosedInputRect.bottom,
|
|
closeTo(keyboardUpInputRect.bottom, 0.1),
|
|
);
|
|
expect(
|
|
keyboardClosedInputRect.height,
|
|
closeTo(keyboardUpInputRect.height, 0.1),
|
|
);
|
|
debugDefaultTargetPlatformOverride = null;
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'should_keep_composer_position_when_keyboard_insets_change_after_settling',
|
|
(tester) async {
|
|
debugDefaultTargetPlatformOverride = TargetPlatform.android;
|
|
addTearDown(() => debugDefaultTargetPlatformOverride = null);
|
|
tester.view.physicalSize = const Size(390, 2200);
|
|
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);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final composer = find.byKey(const Key('comment-composer-sheet'));
|
|
expect(composer, findsOneWidget);
|
|
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 1100);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 90));
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
final settledTop = tester.getTopLeft(composer).dy;
|
|
final settledBottom = tester.getBottomLeft(composer).dy;
|
|
final settledHeight = tester.getSize(composer).height;
|
|
expect(settledBottom, closeTo(2200 - 1100, 0.1));
|
|
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 1150);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 90));
|
|
expect(tester.getTopLeft(composer).dy, closeTo(settledTop, 0.1));
|
|
expect(tester.getBottomLeft(composer).dy, closeTo(settledBottom, 0.1));
|
|
expect(tester.getSize(composer).height, closeTo(settledHeight, 0.1));
|
|
|
|
tester.view.viewInsets = const FakeViewPadding(bottom: 600);
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 90));
|
|
expect(tester.getTopLeft(composer).dy, closeTo(settledTop, 0.1));
|
|
expect(tester.getBottomLeft(composer).dy, closeTo(settledBottom, 0.1));
|
|
expect(tester.getSize(composer).height, closeTo(settledHeight, 0.1));
|
|
|
|
tester.view.viewInsets = const FakeViewPadding();
|
|
await tester.pumpAndSettle();
|
|
expect(composer, findsOneWidget);
|
|
expect(tester.getTopLeft(composer).dy, closeTo(settledTop, 0.1));
|
|
expect(tester.getBottomLeft(composer).dy, closeTo(settledBottom, 0.1));
|
|
expect(tester.getSize(composer).height, closeTo(settledHeight, 0.1));
|
|
debugDefaultTargetPlatformOverride = null;
|
|
},
|
|
);
|
|
|
|
testWidgets('should_keep_composer_open_after_input_loses_focus', (
|
|
tester,
|
|
) async {
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [commentFixture(id: '10', content: 'A comment')],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final composer = find.byKey(const Key('comment-composer-sheet'));
|
|
final input = find.byKey(const Key('comment-composer-input'));
|
|
expect(composer, findsOneWidget);
|
|
|
|
tester.widget<TextField>(input).focusNode?.unfocus();
|
|
await tester.pumpAndSettle();
|
|
expect(composer, findsOneWidget);
|
|
|
|
await tester.tap(input);
|
|
await tester.pump();
|
|
expect(composer, findsOneWidget);
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('should_close_composer_before_comment_panel_from_outside', (
|
|
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);
|
|
final sheet = find.byKey(const Key('player-comments-sheet'));
|
|
expect(
|
|
find.byKey(const Key('player-comments-dismiss-layer')),
|
|
findsOneWidget,
|
|
);
|
|
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
expect(sheet, findsOneWidget);
|
|
final composer = find.byKey(const Key('comment-composer-sheet'));
|
|
expect(composer, findsOneWidget);
|
|
expect(
|
|
find.byKey(const Key('comment-composer-dismiss-layer')),
|
|
findsOneWidget,
|
|
);
|
|
|
|
final sheetRect = tester.getRect(sheet);
|
|
final composerRect = tester.getRect(composer);
|
|
await tester.tap(find.byKey(const Key('comment-composer-input')));
|
|
await tester.pump();
|
|
expect(composer, findsOneWidget);
|
|
|
|
await tester.tapAt(
|
|
Offset(sheetRect.center.dx, (sheetRect.top + composerRect.top) / 2),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(sheet, findsOneWidget);
|
|
expect(composer, findsNothing);
|
|
|
|
await tester.tapAt(const Offset(20, 20));
|
|
await tester.pumpAndSettle();
|
|
expect(sheet, findsNothing);
|
|
});
|
|
|
|
testWidgets('should_hide_keyboard_on_back_without_closing_composer_overlay', (
|
|
tester,
|
|
) async {
|
|
final repository = FakeCommentRepository(
|
|
pages: [
|
|
CommentPage(
|
|
comments: [commentFixture(id: '10', content: 'A comment')],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
await tester.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
|
|
final sheet = find.byKey(const Key('player-comments-sheet'));
|
|
final composer = find.byKey(const Key('comment-composer-sheet'));
|
|
expect(sheet, findsOneWidget);
|
|
expect(composer, findsOneWidget);
|
|
final input = find.byKey(const Key('comment-composer-input'));
|
|
await tester.tap(input);
|
|
await tester.pump();
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isTrue);
|
|
|
|
await tester.binding.handlePopRoute();
|
|
await tester.pumpAndSettle();
|
|
expect(sheet, findsOneWidget);
|
|
expect(composer, findsOneWidget);
|
|
expect(tester.widget<TextField>(input).focusNode?.hasFocus, isFalse);
|
|
|
|
await tester.tapAt(const Offset(20, 20));
|
|
await tester.pumpAndSettle();
|
|
expect(sheet, findsOneWidget);
|
|
expect(composer, findsNothing);
|
|
});
|
|
|
|
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-placeholder'))).height,
|
|
44,
|
|
);
|
|
final placeholderLabel = find.byKey(
|
|
const Key('comment-input-placeholder-label'),
|
|
);
|
|
final placeholderSendIcon = find.descendant(
|
|
of: find.byKey(const Key('comment-post')),
|
|
matching: find.byIcon(Icons.send_rounded),
|
|
);
|
|
expect(
|
|
tester.getCenter(placeholderLabel).dy,
|
|
closeTo(tester.getCenter(placeholderSendIcon).dy, 1),
|
|
);
|
|
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-input-placeholder')),
|
|
);
|
|
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_use_half_screen_height_for_one_comment', (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: 'One comment')],
|
|
),
|
|
],
|
|
);
|
|
|
|
await _pumpSheet(tester, repository: repository);
|
|
|
|
final sheet = find.byKey(const Key('player-comments-sheet'));
|
|
expect(tester.getSize(sheet).height, closeTo(844 / 2, 0.1));
|
|
});
|
|
|
|
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(20))],
|
|
);
|
|
|
|
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,
|
|
closeTo(844 / 2, 0.1),
|
|
);
|
|
});
|
|
|
|
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.tap(find.byKey(const Key('comment-input-placeholder')));
|
|
await tester.pumpAndSettle();
|
|
await tester.enterText(
|
|
find.byKey(const Key('comment-composer-input')),
|
|
'hello badword',
|
|
);
|
|
await tester.pump();
|
|
await tester.tap(find.byKey(const Key('comment-composer-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('comment-composer-sheet')), findsNothing);
|
|
expect(find.text('hello ***'), findsOneWidget);
|
|
expect(find.text('Under review'), findsOneWidget);
|
|
expect(haptics.lightImpacts, 1);
|
|
expect(find.byKey(const Key('comment-input-placeholder')), findsOneWidget);
|
|
});
|
|
|
|
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.pumpAndSettle();
|
|
expect(find.byKey(const Key('comment-composer-sheet')), findsOneWidget);
|
|
expect(find.textContaining('Replying to'), findsOneWidget);
|
|
await tester.enterText(
|
|
find.byKey(const Key('comment-composer-input')),
|
|
'My reply',
|
|
);
|
|
await tester.pump();
|
|
await tester.tap(find.byKey(const Key('comment-composer-post')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(repository.publishCalls.single.parentId, '10');
|
|
expect(find.byKey(const Key('comment-composer-sheet')), findsNothing);
|
|
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,
|
|
Locale locale = const Locale('en'),
|
|
String? episodeId,
|
|
}) 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: locale,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
initialRoute: '/comments',
|
|
routes: {
|
|
'/': (_) => const Scaffold(backgroundColor: AppColors.ink),
|
|
'/comments': (_) => Scaffold(
|
|
backgroundColor: AppColors.ink,
|
|
resizeToAvoidBottomInset: false,
|
|
body: Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: DramaCommentsSheet(drama: _drama, episodeId: episodeId),
|
|
),
|
|
),
|
|
},
|
|
),
|
|
),
|
|
);
|
|
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();
|
|
}
|
|
}
|