feat(flutter): 统一空列表占位组件
This commit is contained in:
1 parent
19babb8f4a
commit
6d08330388
24 files changed
+394
-63
No files matched your search
@@ -12,6 +12,7 @@ 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/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';
|
||||
@@ -68,6 +69,19 @@ void main() {
|
||||
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 {
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:fruitshorts/core/data/services/auth_controller.dart';
|
||||
import 'package:fruitshorts/core/data/services/auth_session_store.dart';
|
||||
import 'package:fruitshorts/core/data/services/download_controller.dart';
|
||||
import 'package:fruitshorts/core/platform/haptics.dart';
|
||||
import 'package:fruitshorts/core/widgets/empty_state.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/downloads/downloads_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -90,6 +91,7 @@ void main() {
|
||||
await _pumpPage(tester, auth: auth, controller: controller);
|
||||
|
||||
expect(find.byKey(const Key('downloads-empty')), findsOneWidget);
|
||||
expect(find.byType(EmptyState), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_show_retry_when_policy_or_list_load_fails', (
|
||||
@@ -163,6 +165,7 @@ void main() {
|
||||
expect(repository.clearCalls, 1);
|
||||
expect(haptics.mediumImpacts, 1);
|
||||
expect(find.byKey(const Key('downloads-empty')), findsOneWidget);
|
||||
expect(find.byType(EmptyState), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:fruitshorts/core/theme/app_colors.dart';
|
||||
import 'package:fruitshorts/core/theme/app_theme.dart';
|
||||
import 'package:fruitshorts/core/widgets/empty_state.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_render_title_subtitle_action_and_title_only_semantics', (
|
||||
tester,
|
||||
) async {
|
||||
var taps = 0;
|
||||
await _pump(
|
||||
tester,
|
||||
theme: AppTheme.dark(),
|
||||
child: EmptyState(
|
||||
icon: Icons.movie_outlined,
|
||||
title: 'No stories',
|
||||
subtitle: 'New content will appear here.',
|
||||
actionLabel: 'Retry',
|
||||
onAction: () => taps++,
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byKey(const Key('empty-state-art')), findsOneWidget);
|
||||
expect(find.byKey(const Key('empty-state-icon')), findsOneWidget);
|
||||
expect(find.text('No stories'), findsOneWidget);
|
||||
expect(find.text('New content will appear here.'), findsOneWidget);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('empty-state-art'))),
|
||||
const Size(96, 96),
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('empty-state-icon'))),
|
||||
const Size(42, 42),
|
||||
);
|
||||
expect(
|
||||
tester
|
||||
.widget<Text>(find.byKey(const Key('empty-state-title')))
|
||||
.style
|
||||
?.fontSize,
|
||||
16,
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('empty-state-action'))).height,
|
||||
greaterThanOrEqualTo(44),
|
||||
);
|
||||
|
||||
final semantics = tester.ensureSemantics();
|
||||
expect(find.bySemanticsLabel('No stories'), findsOneWidget);
|
||||
expect(
|
||||
find.bySemanticsLabel('New content will appear here.'),
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
await tester.tap(find.byKey(const Key('empty-state-action')));
|
||||
expect(taps, 1);
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('should_render_compact_dimensions_for_embedded_empty_state', (
|
||||
tester,
|
||||
) async {
|
||||
await _pump(
|
||||
tester,
|
||||
child: const EmptyState(
|
||||
icon: Icons.chat_bubble_outline_rounded,
|
||||
title: 'No comments yet.',
|
||||
compact: true,
|
||||
),
|
||||
);
|
||||
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('empty-state-art'))),
|
||||
const Size(64, 64),
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('empty-state-icon'))),
|
||||
const Size(28, 28),
|
||||
);
|
||||
expect(
|
||||
tester
|
||||
.widget<Text>(find.byKey(const Key('empty-state-title')))
|
||||
.style
|
||||
?.fontSize,
|
||||
14,
|
||||
);
|
||||
expect(find.byKey(const Key('empty-state-action')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_use_theme_aware_icon_colors', (tester) async {
|
||||
await _pump(
|
||||
tester,
|
||||
theme: AppTheme.dark(),
|
||||
child: const EmptyState(icon: Icons.history_rounded, title: 'No history'),
|
||||
);
|
||||
expect(
|
||||
tester.widget<Icon>(find.byKey(const Key('empty-state-icon'))).color,
|
||||
AppColors.blueSoft,
|
||||
);
|
||||
|
||||
await _pump(
|
||||
tester,
|
||||
theme: AppTheme.light(),
|
||||
child: const EmptyState(icon: Icons.history_rounded, title: 'No history'),
|
||||
);
|
||||
expect(
|
||||
tester.widget<Icon>(find.byKey(const Key('empty-state-icon'))).color,
|
||||
AppColors.blue,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('should_fit_compact_width_without_overflow', (tester) async {
|
||||
tester.view.physicalSize = const Size(320, 568);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
|
||||
await _pump(
|
||||
tester,
|
||||
child: const EmptyState(
|
||||
icon: Icons.download_outlined,
|
||||
title: 'No download authorizations yet.',
|
||||
subtitle: 'New content will appear here.',
|
||||
actionLabel: 'Retry',
|
||||
),
|
||||
);
|
||||
|
||||
final content = tester.widget<ConstrainedBox>(
|
||||
find.byKey(const Key('empty-state-content')),
|
||||
);
|
||||
expect(content.constraints.maxWidth, 280);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pump(
|
||||
WidgetTester tester, {
|
||||
ThemeData? theme,
|
||||
required Widget child,
|
||||
}) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme ?? AppTheme.dark(),
|
||||
home: Scaffold(body: child),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'package:fruitshorts/core/data/services/auth_controller.dart';
|
||||
import 'package:fruitshorts/core/data/services/auth_session_store.dart';
|
||||
import 'package:fruitshorts/core/data/services/favorite_controller.dart';
|
||||
import 'package:fruitshorts/core/platform/haptics.dart';
|
||||
import 'package:fruitshorts/core/widgets/empty_state.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/favorites/favorites_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -83,6 +84,7 @@ void main() {
|
||||
await _pumpPage(tester, auth: auth, favorites: favorites);
|
||||
|
||||
expect(find.byKey(const Key('favorites-empty')), findsOneWidget);
|
||||
expect(find.byType(EmptyState), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_show_retry_when_favorite_list_fails', (tester) async {
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:fruitshorts/core/data/models/notice.dart';
|
||||
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/notice_repository.dart';
|
||||
import 'package:fruitshorts/core/formatting/localized_date_time.dart';
|
||||
import 'package:fruitshorts/core/widgets/empty_state.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/notices/notices_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -39,6 +40,7 @@ void main() {
|
||||
);
|
||||
|
||||
expect(find.byKey(const Key('notices-empty')), findsOneWidget);
|
||||
expect(find.byType(EmptyState), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_show_backend_error_and_retry_when_page_fails', (
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:fruitshorts/core/data/repositories/watch_history_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/widgets/empty_state.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/watch_records/watch_records_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -124,6 +125,7 @@ void main() {
|
||||
expect(history.clearCalls, 1);
|
||||
expect(haptics.mediumImpacts, 1);
|
||||
expect(find.text('No watch history yet.'), findsOneWidget);
|
||||
expect(find.byType(EmptyState), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user