// Author: fruit.com.cn import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/theme/app_colors.dart'; import 'package:fruitshorts/core/widgets/app_glow_panel.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/player/widgets/player_more_sheet.dart'; void main() { testWidgets('should_hide_pip_row_when_platform_unsupported', (tester) async { await _openSheet(tester); expect(find.byKey(const Key('player-more-sheet')), findsOneWidget); expect(find.byKey(const Key('player-more-clear')), findsOneWidget); expect(find.byKey(const Key('player-more-offline')), findsOneWidget); expect(find.byKey(const Key('player-more-pip')), findsNothing); expect(find.byKey(const Key('player-more-close')), findsNothing); }); testWidgets('should_render_reference_layout_metrics', (tester) async { await _openSheet(tester); // 弹框统一换成 AppGlowPanel 渐变外壳:外壳是主题红晕渐变,内层 Material 透明。 final shell = tester.widget( find .descendant( of: find.byType(AppGlowPanel), matching: find.byType(DecoratedBox), ) .first, ); final shellDecoration = shell.decoration as BoxDecoration; expect((shellDecoration.gradient! as LinearGradient).colors, [ AppColors.followingGlow, AppColors.followingGlowMid, const Color(0xFF101012), ]); expect( tester.widget(find.byKey(const Key('player-more-sheet'))).color, isNull, ); expect( tester.getSize(find.byKey(const Key('player-more-drag-handle'))), const Size(36, 4), ); for (final key in const [ Key('player-more-speed-row'), Key('player-more-quality-row'), Key('player-more-clear'), Key('player-more-offline'), ]) { expect(tester.getSize(find.byKey(key)).height, 56); } for (final key in const [ Key('player-more-speed-track'), Key('player-more-quality-track'), ]) { expect(tester.getSize(find.byKey(key)).height, 34); final trackDecoration = tester.widget(find.byKey(key)).decoration! as BoxDecoration; // 渐变外壳上的分段轨道改成 8% 白色叠层,不再用实色 raised。 expect(trackDecoration.color, Colors.white.withValues(alpha: 0.08)); } for (final key in const [ Key('player-more-speed-0.75'), Key('player-more-speed-1.0'), Key('player-more-speed-1.25'), ]) { expect(tester.getSize(find.byKey(key)).height, 26); } final speedLabel = tester.widget( find .descendant( of: find.byKey(const Key('player-more-speed-row')), matching: find.byType(Text), ) .first, ); expect(speedLabel.style?.fontSize, 15); expect(speedLabel.style?.fontWeight, FontWeight.w500); expect(tester.widget(find.byIcon(Icons.speed_outlined)).size, 22); expect(tester.widget(find.byIcon(Icons.fullscreen)).size, 22); final selected = tester.widget( find .descendant( of: find.byKey(const Key('player-more-speed-1.0')), matching: find.byType(Container), ) .first, ); final selectedDecoration = selected.decoration! as BoxDecoration; // 选中态同样改成 16% 白色叠层。 expect(selectedDecoration.color, Colors.white.withValues(alpha: 0.16)); expect(selectedDecoration.boxShadow, isNotEmpty); final selectedText = tester.widget( find.descendant( of: find.byKey(const Key('player-more-speed-1.0')), matching: find.text('1.0x'), ), ); expect(selectedText.style?.color, AppColors.onSurface); expect(selectedText.style?.fontSize, 13); expect(selectedText.style?.fontWeight, FontWeight.w600); final unselected = tester.widget( find .descendant( of: find.byKey(const Key('player-more-speed-1.25')), matching: find.byType(Container), ) .first, ); final unselectedDecoration = unselected.decoration! as BoxDecoration; expect(unselectedDecoration.color, Colors.transparent); final unselectedText = tester.widget( find.descendant( of: find.byKey(const Key('player-more-speed-1.25')), matching: find.text('1.25x'), ), ); expect(unselectedText.style?.color, AppColors.muted); expect(unselectedText.style?.fontSize, 13); expect(unselectedText.style?.fontWeight, FontWeight.w400); final disabledText = tester.widget( find.descendant( of: find.byKey(const Key('player-more-quality-720P')), matching: find.byType(Text), ), ); expect(disabledText.style?.color, AppColors.subtle); }); testWidgets('should_enable_quality_when_ticket_uses_lowercase_codes', ( tester, ) async { await _openSheet( tester, qualities: const ['AUTO', '360p', '480p', '720p', '1080p'], ); expect(find.text('720P'), findsOneWidget); expect(find.text('1080P'), findsOneWidget); expect(find.textContaining('暂不可用'), findsNothing); final enabled = tester.widget( find.descendant( of: find.byKey(const Key('player-more-quality-720P')), matching: find.byType(Text), ), ); expect(enabled.style?.color, isNot(AppColors.subtle)); }); testWidgets('should_report_selected_quality_when_a_rendition_is_tapped', ( tester, ) async { String? picked; await _openSheet( tester, qualities: const ['AUTO', '720p'], onQuality: (quality) async { picked = quality; return true; }, ); await tester.ensureVisible(find.byKey(const Key('player-more-quality-720P'))); await tester.tap(find.byKey(const Key('player-more-quality-720P'))); await tester.pump(); expect(picked, '720P'); final selected = tester.widget( find.descendant( of: find.byKey(const Key('player-more-quality-720P')), matching: find.byType(Text), ), ); expect(selected.style?.color, AppColors.onSurface); }); testWidgets('should_restore_previous_quality_when_switch_fails', ( tester, ) async { await _openSheet( tester, qualities: const ['AUTO', '720p'], onQuality: (_) async => false, ); await tester.ensureVisible(find.byKey(const Key('player-more-quality-720P'))); await tester.tap(find.byKey(const Key('player-more-quality-720P'))); await tester.pumpAndSettle(); final restored = tester.widget( find.descendant( of: find.byKey(const Key('player-more-quality-AUTO')), matching: find.byType(Text), ), ); expect(restored.style?.color, AppColors.onSurface); }); testWidgets('should_trigger_pip_and_close_sheet_when_tapped', (tester) async { var pipCalls = 0; await _openSheet(tester, onPip: () => pipCalls++); expect(find.byKey(const Key('player-more-pip')), findsOneWidget); expect( tester.getTopLeft(find.byKey(const Key('player-more-clear'))).dy < tester.getTopLeft(find.byKey(const Key('player-more-pip'))).dy, isTrue, ); expect( tester.getTopLeft(find.byKey(const Key('player-more-pip'))).dy < tester.getTopLeft(find.byKey(const Key('player-more-offline'))).dy, isTrue, ); await tester.tap(find.byKey(const Key('player-more-pip'))); await tester.pumpAndSettle(); expect(pipCalls, 1); expect(find.byKey(const Key('player-more-sheet')), findsNothing); }); } Future _openSheet( WidgetTester tester, { VoidCallback? onPip, Future Function(String quality)? onQuality, List qualities = const ['AUTO'], }) async { await tester.pumpWidget( MaterialApp( supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates, home: Scaffold( body: Builder( builder: (context) => Center( child: TextButton( key: const Key('open-more-sheet'), onPressed: () => showModalBottomSheet( context: context, backgroundColor: Colors.transparent, isScrollControlled: true, builder: (_) => PlayerMoreSheet( l10n: AppLocalizations.of(context)!, state: PlayerMoreState(speed: 1.0, qualities: qualities), actions: PlayerMoreActions( onSpeedChanged: (_) {}, onClear: () {}, onDownload: () {}, onQualityChanged: onQuality ?? (_) async => true, onPip: onPip, ), ), ), child: const Text('open'), ), ), ), ), ), ); await tester.tap(find.byKey(const Key('open-more-sheet'))); await tester.pumpAndSettle(); }