style(app): 格式化并拆分长函数

This commit is contained in:
Zhou Weidong committed 2026-10-20 04:38:36 +08:00
1 parent 2b2b7e9913
commit 7850dfc746
31 files changed
+388 -318

No files matched your search

+2 -1
View File
@@ -146,7 +146,8 @@ class _LumaDramaAppState extends State<LumaDramaApp> {
); );
late final CommerceRepository _commerceRepository = late final CommerceRepository _commerceRepository =
widget.commerceRepository ?? HttpCommerceRepository(CommerceApi(_apiClient)); widget.commerceRepository ??
HttpCommerceRepository(CommerceApi(_apiClient));
late final WalletController _walletController = WalletController( late final WalletController _walletController = WalletController(
_commerceRepository, _commerceRepository,
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'json_numbers.dart'; import 'json_numbers.dart';
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'json_numbers.dart'; import 'json_numbers.dart';
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'drama.dart'; import 'drama.dart';
import 'json_numbers.dart'; import 'json_numbers.dart';
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import '../models/order.dart'; import '../models/order.dart';
import '../models/product.dart'; import '../models/product.dart';
@@ -59,7 +59,8 @@ class _CachedTicket {
final PlayTicket ticket; final PlayTicket ticket;
final DateTime fetchedAt; final DateTime fetchedAt;
bool isExpired(DateTime now, Duration ttl) => now.difference(fetchedAt) >= ttl; bool isExpired(DateTime now, Duration ttl) =>
now.difference(fetchedAt) >= ttl;
} }
/// 测试与离线场景用:按集返回固定票据,并记录被请求过几次。 /// 测试与离线场景用:按集返回固定票据,并记录被请求过几次。
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import '../models/search_result.dart'; import '../models/search_result.dart';
import '../remote/catalog_api.dart'; import '../remote/catalog_api.dart';
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import '../models/order.dart'; import '../models/order.dart';
import '../models/product.dart'; import '../models/product.dart';
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
+2 -6
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import '../../core/data/models/drama.dart'; import '../../core/data/models/drama.dart';
import '../../core/data/repositories/search_repository.dart'; import '../../core/data/repositories/search_repository.dart';
@@ -10,9 +10,5 @@ class DiscoverBinding {
required Drama drama, required Drama drama,
required WatchHistoryRepository history, required WatchHistoryRepository history,
SearchRepository? search, SearchRepository? search,
}) => DiscoverLogic( }) => DiscoverLogic(drama: drama, history: history, searchRepository: search);
drama: drama,
history: history,
searchRepository: search,
);
} }
+2 -1
View File
@@ -134,7 +134,8 @@ class DiscoverLogic extends ChangeNotifier {
void _scheduleSearch(String query) { void _scheduleSearch(String query) {
final alreadyRequested = query == _lastRequestedQuery; final alreadyRequested = query == _lastRequestedQuery;
if (alreadyRequested && (searchLoading || searchResults.isNotEmpty || searchError != null)) { if (alreadyRequested &&
(searchLoading || searchResults.isNotEmpty || searchError != null)) {
return; return;
} }
_lastRequestedQuery = query; _lastRequestedQuery = query;
+3 -1
View File
@@ -181,7 +181,8 @@ class _DiscoverPageState extends State<DiscoverPage> {
padding: const EdgeInsets.fromLTRB(0, 0, 0, 12), padding: const EdgeInsets.fromLTRB(0, 0, 0, 12),
child: _languages(l10n, allDramas), child: _languages(l10n, allDramas),
), ),
), if (logic.resumeRecord != null && logic.resumeDrama(allDramas) != null) ),
if (logic.resumeRecord != null && logic.resumeDrama(allDramas) != null)
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 14), padding: const EdgeInsets.fromLTRB(16, 0, 16, 14),
@@ -495,6 +496,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
if (code == 'en') return l10n.languageEnglish; if (code == 'en') return l10n.languageEnglish;
return code; return code;
} }
Widget _continueCard( Widget _continueCard(
AppLocalizations l10n, AppLocalizations l10n,
Drama drama, Drama drama,
+55 -45
View File
@@ -25,24 +25,40 @@ class _HomeDramaGrid extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loading = _loadingState();
if (loading != null) return loading;
final search = _searchState();
if (search != null) return search;
if (dramas.isEmpty) return _emptyState();
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: _masonry(dramas)),
const SliverToBoxAdapter(child: SizedBox(height: 24)),
],
);
}
Widget? _loadingState() {
if (categories.loading && categories.feed.sections.isEmpty) { if (categories.loading && categories.feed.sections.isEmpty) {
return const SliverFillRemaining( return const SliverFillRemaining(
hasScrollBody: false, hasScrollBody: false,
child: Center(child: CircularProgressIndicator(strokeWidth: 2)), child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
); );
} }
if (categories.error != null) { if (categories.error == null) return null;
return _fill( return _fill(
EmptyState( EmptyState(
key: const Key('home-categories-error'), key: const Key('home-categories-error'),
icon: Icons.cloud_off_rounded, icon: Icons.cloud_off_rounded,
title: l10n.categoriesError, title: l10n.categoriesError,
subtitle: l10n.emptyHint, subtitle: l10n.emptyHint,
actionLabel: l10n.retry, actionLabel: l10n.retry,
onAction: categories.load, onAction: categories.load,
), ),
); );
} }
Widget? _searchState() {
if (searching && searchLoading) { if (searching && searchLoading) {
return const SliverFillRemaining( return const SliverFillRemaining(
hasScrollBody: false, hasScrollBody: false,
@@ -54,39 +70,33 @@ class _HomeDramaGrid extends StatelessWidget {
), ),
); );
} }
if (searching && searchError != null) { if (!searching || searchError == null) return null;
return _fill( return _fill(
EmptyState( EmptyState(
key: const Key('discover-search-error'), key: const Key('discover-search-error'),
icon: Icons.cloud_off_rounded, icon: Icons.cloud_off_rounded,
title: errorText(l10n, searchError, l10n.searchFailed), title: errorText(l10n, searchError, l10n.searchFailed),
actionLabel: l10n.retry, actionLabel: l10n.retry,
actionKey: const Key('discover-search-retry'), actionKey: const Key('discover-search-retry'),
onAction: onRetrySearch, onAction: onRetrySearch,
), ),
); );
} }
if (dramas.isEmpty) {
return _fill( Widget _emptyState() {
EmptyState( return _fill(
key: const Key('home-all-dramas-empty'), EmptyState(
icon: searching key: const Key('home-all-dramas-empty'),
? Icons.search_off_rounded icon: searching
: Icons.movie_filter_outlined, ? Icons.search_off_rounded
title: searching : Icons.movie_filter_outlined,
? l10n.noSearchResults title: searching
: categories.selectedSectionMissing ? l10n.noSearchResults
? l10n.noCategoryDramas : categories.selectedSectionMissing
: l10n.noSearchResults, ? l10n.noCategoryDramas
subtitle: l10n.emptyHint, : l10n.noSearchResults,
), subtitle: l10n.emptyHint,
); ),
}
return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(child: _masonry(dramas)),
const SliverToBoxAdapter(child: SizedBox(height: 24)),
],
); );
} }
+1 -4
View File
@@ -283,10 +283,7 @@ class PlayerLogic extends ChangeNotifier {
} }
final ready = await ensureTicket(index, forceRefresh: true); final ready = await ensureTicket(index, forceRefresh: true);
if (ready) { if (ready) {
await session.select( await session.select(index, preloadIndex: nextVisibleIndex(index));
index,
preloadIndex: nextVisibleIndex(index),
);
} }
return ready; return ready;
} finally { } finally {
+2 -1
View File
@@ -991,7 +991,8 @@ class _PlaybackOverlayState extends State<_PlaybackOverlay> {
Navigator.pop(sheetContext); Navigator.pop(sheetContext);
widget.onQualityTap(); widget.onQualityTap();
}, },
), ListTile( ),
ListTile(
key: const Key('player-more-download'), key: const Key('player-more-download'),
leading: const Icon( leading: const Icon(
Icons.download_outlined, Icons.download_outlined,
+50 -44
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -26,54 +26,60 @@ class QualitySheet extends StatelessWidget {
key: const Key('player-quality-sheet'), key: const Key('player-quality-sheet'),
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( _header(l10n),
padding: const EdgeInsets.fromLTRB(20, 2, 20, 8), for (final quality in qualities) _qualityTile(l10n, quality),
child: Align( _hint(l10n),
alignment: Alignment.centerLeft,
child: Text(
l10n.playbackQuality,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
),
),
for (final quality in qualities)
ListTile(
key: Key('player-quality-$quality'),
dense: true,
title: Text(
_label(l10n, quality),
style: TextStyle(
color: quality == selected ? AppColors.blue : AppColors.cream,
fontSize: 13,
fontWeight: quality == selected
? FontWeight.w700
: FontWeight.w500,
),
),
trailing: quality == selected
? const Icon(
Icons.check_rounded,
color: AppColors.blue,
size: 18,
)
: null,
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 6, 20, 14),
child: Text(
l10n.qualityAutoHint,
style: const TextStyle(color: AppColors.muted, fontSize: 11),
),
),
], ],
), ),
); );
} }
Widget _header(AppLocalizations l10n) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 2, 20, 8),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
l10n.playbackQuality,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
),
);
}
Widget _qualityTile(AppLocalizations l10n, String quality) {
final active = quality == selected;
return ListTile(
key: Key('player-quality-$quality'),
dense: true,
title: Text(
_label(l10n, quality),
style: TextStyle(
color: active ? AppColors.blue : AppColors.cream,
fontSize: 13,
fontWeight: active ? FontWeight.w700 : FontWeight.w500,
),
),
trailing: active
? const Icon(Icons.check_rounded, color: AppColors.blue, size: 18)
: null,
);
}
Widget _hint(AppLocalizations l10n) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 6, 20, 14),
child: Text(
l10n.qualityAutoHint,
style: const TextStyle(color: AppColors.muted, fontSize: 11),
),
);
}
String _label(AppLocalizations l10n, String quality) => String _label(AppLocalizations l10n, String quality) =>
quality == autoQuality ? l10n.qualityAuto : quality; quality == autoQuality ? l10n.qualityAuto : quality;
} }
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
+77 -70
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'dart:async'; import 'dart:async';
@@ -122,7 +122,9 @@ class _StorePageState extends State<StorePage> {
controller.purchaseError, controller.purchaseError,
l10n.orderCreateFailed, l10n.orderCreateFailed,
); );
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message))); ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(message)));
return; return;
} }
widget.onPurchased?.call(); widget.onPurchased?.call();
@@ -162,80 +164,85 @@ class _ProductTile extends StatelessWidget {
children: [ children: [
_ProductBadge(vip: product.isVip), _ProductBadge(vip: product.isVip),
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(child: _info(l10n)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
key: Key('store-product-name-${product.id}'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: 14,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 4),
Text(
_detail(l10n),
style: const TextStyle(
color: AppColors.muted,
fontSize: 11,
),
),
],
),
),
const SizedBox(width: 10), const SizedBox(width: 10),
Column( _price(l10n),
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
product.displayPrice,
key: Key('store-product-price-${product.id}'),
style: const TextStyle(
color: AppColors.gold,
fontSize: 13,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 8),
SizedBox(
width: 88,
height: 32,
child: FilledButton(
key: Key('store-buy-${product.id}'),
onPressed: purchasing ? null : onBuy,
style: FilledButton.styleFrom(
backgroundColor: AppColors.blue,
foregroundColor: AppColors.onPrimary,
padding: EdgeInsets.zero,
textStyle: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
),
),
child: purchasing
? const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.onPrimary,
),
)
: Text(l10n.buy),
),
),
],
),
], ],
), ),
); );
} }
Widget _info(AppLocalizations l10n) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
key: Key('store-product-name-${product.id}'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.onSurface,
fontSize: 14,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 4),
Text(
_detail(l10n),
style: const TextStyle(color: AppColors.muted, fontSize: 11),
),
],
);
}
Widget _price(AppLocalizations l10n) {
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
product.displayPrice,
key: Key('store-product-price-${product.id}'),
style: const TextStyle(
color: AppColors.gold,
fontSize: 13,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 8),
SizedBox(
width: 88,
height: 32,
child: FilledButton(
key: Key('store-buy-${product.id}'),
onPressed: purchasing ? null : onBuy,
style: FilledButton.styleFrom(
backgroundColor: AppColors.blue,
foregroundColor: AppColors.onPrimary,
padding: EdgeInsets.zero,
textStyle: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
),
),
child: purchasing ? _buyProgress() : Text(l10n.buy),
),
),
],
);
}
Widget _buyProgress() {
return const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
color: AppColors.onPrimary,
),
);
}
String _detail(AppLocalizations l10n) { String _detail(AppLocalizations l10n) {
if (product.isVip) return l10n.vipDuration(product.vipDays); if (product.isVip) return l10n.vipDuration(product.vipDays);
final base = l10n.walletBalanceShort(product.coinAmount); final base = l10n.walletBalanceShort(product.coinAmount);
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
+5 -4
View File
@@ -90,9 +90,8 @@ class _WalletPageState extends State<WalletPage> {
void _openRecharge(WalletController wallet) { void _openRecharge(WalletController wallet) {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute<void>( MaterialPageRoute<void>(
builder: (_) => RechargePage( builder: (_) =>
onPurchased: () => unawaited(wallet.load()), RechargePage(onPurchased: () => unawaited(wallet.load())),
),
), ),
); );
} }
@@ -320,7 +319,9 @@ class _LedgerTile extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final positive = entry.changeAmount >= 0; final positive = entry.changeAmount >= 0;
final color = positive ? AppColors.success : AppColors.accent; final color = positive ? AppColors.success : AppColors.accent;
final amount = positive ? '+${entry.changeAmount}' : '${entry.changeAmount}'; final amount = positive
? '+${entry.changeAmount}'
: '${entry.changeAmount}';
return Container( return Container(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
+41 -38
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'dart:convert'; import 'dart:convert';
@@ -9,43 +9,46 @@ import 'package:fruitshorts/core/data/remote/catalog_api.dart';
import '../support/fake_http_adapter.dart'; import '../support/fake_http_adapter.dart';
void main() { void main() {
test('should_request_search_with_keyword_and_paging_when_searching', () async { test(
final adapter = FakeHttpAdapter( 'should_request_search_with_keyword_and_paging_when_searching',
(options) async => FakeHttpResponse( () async {
jsonEncode({ final adapter = FakeHttpAdapter(
'code': 0, (options) async => FakeHttpResponse(
'message': 'ok', jsonEncode({
'data': { 'code': 0,
'total': 1, 'message': 'ok',
'list': [ 'data': {
{ 'total': 1,
'id': '2111012753262120962', 'list': [
'title': 'Velvet Nights', {
'synopsis': 'A city romance', 'id': '2111012753262120962',
'cover': '/files/velvet.png', 'title': 'Velvet Nights',
'genre': 'Romance', 'synopsis': 'A city romance',
'totalEpisodes': 12, 'cover': '/files/velvet.png',
'isHot': true, 'genre': 'Romance',
'language': 'id', 'totalEpisodes': 12,
}, 'isHot': true,
], 'language': 'id',
}, },
}), ],
), },
); }),
final api = CatalogApi( ),
ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter), );
); final api = CatalogApi(
ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter),
);
final page = await api.search('velvet'); final page = await api.search('velvet');
final request = adapter.requests.single; final request = adapter.requests.single;
expect(request.uri.path, '/app/search'); expect(request.uri.path, '/app/search');
expect(request.queryParameters['keyword'], 'velvet'); expect(request.queryParameters['keyword'], 'velvet');
expect(request.queryParameters['pageIndex'], '1'); expect(request.queryParameters['pageIndex'], '1');
expect(request.queryParameters['pageSize'], '20'); expect(request.queryParameters['pageSize'], '20');
expect(page.total, 1); expect(page.total, 1);
expect(page.list.single.title, 'Velvet Nights'); expect(page.list.single.title, 'Velvet Nights');
expect(page.list.single.language, 'id'); expect(page.list.single.language, 'id');
}); },
);
} }
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'dart:convert'; import 'dart:convert';
+2 -6
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/order.dart'; import 'package:fruitshorts/core/data/models/order.dart';
@@ -81,11 +81,7 @@ AppProduct _product(String name, {int coinAmount = 550, int bonusCoin = 0}) =>
); );
class _FakeStoreRepository implements StoreRepository { class _FakeStoreRepository implements StoreRepository {
_FakeStoreRepository({ _FakeStoreRepository({this.products = const [], this.order, this.orderError});
this.products = const [],
this.order,
this.orderError,
});
final List<AppProduct> products; final List<AppProduct> products;
final AppOrder? order; final AppOrder? order;
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
+107 -59
View File
@@ -8,7 +8,11 @@ import 'package:fruitshorts/pages/discover/discover_logic.dart';
import '../support/fake_watch_history.dart'; import '../support/fake_watch_history.dart';
Drama _drama(String id, {List<int> numbers = const [1, 2, 3], String language = ''}) => Drama( Drama _drama(
String id, {
List<int> numbers = const [1, 2, 3],
String language = '',
}) => Drama(
language: language, language: language,
id: id, id: id,
title: id, title: id,
@@ -29,15 +33,18 @@ Drama _drama(String id, {List<int> numbers = const [1, 2, 3], String language =
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
test('should_resolve_resume_position_of_episode_one_when_constructed', () async { test(
final history = FakeWatchHistory()..saved = const Duration(seconds: 4); 'should_resolve_resume_position_of_episode_one_when_constructed',
final logic = DiscoverLogic(history: history, drama: _drama('signal')); () async {
final history = FakeWatchHistory()..saved = const Duration(seconds: 4);
final logic = DiscoverLogic(history: history, drama: _drama('signal'));
expect(await logic.position, const Duration(seconds: 4)); expect(await logic.position, const Duration(seconds: 4));
expect(history.reads, ['signal']); expect(history.reads, ['signal']);
expect(logic.searchOpen, isFalse); expect(logic.searchOpen, isFalse);
logic.dispose(); logic.dispose();
}); },
);
test('should_reread_resume_position_when_drama_is_swapped', () async { test('should_reread_resume_position_when_drama_is_swapped', () async {
final history = FakeWatchHistory(); final history = FakeWatchHistory();
@@ -51,22 +58,28 @@ void main() {
logic.dispose(); logic.dispose();
}); });
test('should_resume_from_last_watch_when_list_card_has_no_episodes', () async { test(
final history = FakeWatchHistory() 'should_resume_from_last_watch_when_list_card_has_no_episodes',
..saved = const Duration(seconds: 9) () async {
..lastEpisode = 'signal-2'; final history = FakeWatchHistory()
final logic = DiscoverLogic( ..saved = const Duration(seconds: 9)
history: history, ..lastEpisode = 'signal-2';
drama: _drama('signal', numbers: const []), final logic = DiscoverLogic(
); history: history,
drama: _drama('signal', numbers: const []),
);
expect(await logic.position, const Duration(seconds: 9)); expect(await logic.position, const Duration(seconds: 9));
expect(history.reads, ['signal']); expect(history.reads, ['signal']);
logic.dispose(); logic.dispose();
}); },
);
test('should_filter_visible_dramas_when_language_is_selected', () { test('should_filter_visible_dramas_when_language_is_selected', () {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
final dramas = [ final dramas = [
_drama('zh', language: 'zh-CN'), _drama('zh', language: 'zh-CN'),
_drama('id', language: 'id'), _drama('id', language: 'id'),
@@ -84,7 +97,10 @@ void main() {
}); });
test('should_list_distinct_languages_when_feed_has_cards', () { test('should_list_distinct_languages_when_feed_has_cards', () {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
final dramas = [ final dramas = [
_drama('zh', language: 'zh-CN'), _drama('zh', language: 'zh-CN'),
_drama('id', language: 'id'), _drama('id', language: 'id'),
@@ -109,7 +125,10 @@ void main() {
}); });
test('should_show_search_when_flag_is_on_or_text_is_typed', () { test('should_show_search_when_flag_is_on_or_text_is_typed', () {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
expect(logic.showSearch, isFalse); expect(logic.showSearch, isFalse);
@@ -126,7 +145,10 @@ void main() {
}); });
test('should_match_by_id_and_tolerate_missing_id_when_looking_up_drama', () { test('should_match_by_id_and_tolerate_missing_id_when_looking_up_drama', () {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
final catalog = [_drama('signal'), _drama('city')]; final catalog = [_drama('signal'), _drama('city')];
expect(logic.dramaFor('city', catalog)?.id, 'city'); expect(logic.dramaFor('city', catalog)?.id, 'city');
@@ -167,7 +189,12 @@ void main() {
// Staying active with a changed drama re-reads history. // Staying active with a changed drama re-reads history.
history.reads.clear(); history.reads.clear();
sync(_drama('vengeance'), active: true, wasActive: true, dramaChanged: true); sync(
_drama('vengeance'),
active: true,
wasActive: true,
dramaChanged: true,
);
expect(logic.drama.id, 'vengeance'); expect(logic.drama.id, 'vengeance');
expect(history.reads, ['vengeance']); expect(history.reads, ['vengeance']);
@@ -179,43 +206,50 @@ void main() {
logic.dispose(); logic.dispose();
}); });
testWidgets('should_open_and_focus_field_when_search_is_requested_and_page_is_active', ( testWidgets(
tester, 'should_open_and_focus_field_when_search_is_requested_and_page_is_active',
) async { (tester) async {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
await tester.pumpWidget( history: FakeWatchHistory(),
MaterialApp( drama: _drama('s'),
home: Scaffold( );
body: TextField( await tester.pumpWidget(
controller: logic.search, MaterialApp(
focusNode: logic.searchFocus, home: Scaffold(
body: TextField(
controller: logic.search,
focusNode: logic.searchFocus,
),
), ),
), ),
), );
);
logic.syncDrama( logic.syncDrama(
active: true, active: true,
wasActive: true, wasActive: true,
nextDrama: logic.drama, nextDrama: logic.drama,
dramaChanged: false, dramaChanged: false,
searchRequest: 1, searchRequest: 1,
previousSearchRequest: 0, previousSearchRequest: 0,
isActive: () => true, isActive: () => true,
); );
expect(logic.searchOpen, isTrue); expect(logic.searchOpen, isTrue);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(logic.searchFocus.hasFocus, isTrue); expect(logic.searchFocus.hasFocus, isTrue);
await tester.pumpWidget(const SizedBox.shrink()); await tester.pumpWidget(const SizedBox.shrink());
logic.dispose(); logic.dispose();
}); },
);
testWidgets('should_keep_search_field_closed_when_page_is_hidden', ( testWidgets('should_keep_search_field_closed_when_page_is_hidden', (
tester, tester,
) async { ) async {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Scaffold( home: Scaffold(
@@ -250,7 +284,10 @@ void main() {
testWidgets('should_return_choice_and_forward_it_when_act_runs_embedded', ( testWidgets('should_return_choice_and_forward_it_when_act_runs_embedded', (
tester, tester,
) async { ) async {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
final forwarded = <DiscoverPageAction>[]; final forwarded = <DiscoverPageAction>[];
final popped = Completer<DiscoverPageAction?>(); final popped = Completer<DiscoverPageAction?>();
late BuildContext pageContext; late BuildContext pageContext;
@@ -297,7 +334,10 @@ void main() {
testWidgets('should_show_artwork_notice_when_card_has_no_playable_media', ( testWidgets('should_show_artwork_notice_when_card_has_no_playable_media', (
tester, tester,
) async { ) async {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
late BuildContext context; late BuildContext context;
await tester.pumpWidget( await tester.pumpWidget(
@@ -322,8 +362,13 @@ void main() {
logic.dispose(); logic.dispose();
}); });
testWidgets('should_forward_chosen_drama_when_card_is_embedded', (tester) async { testWidgets('should_forward_chosen_drama_when_card_is_embedded', (
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); tester,
) async {
final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
late BuildContext context; late BuildContext context;
Drama? selected; Drama? selected;
@@ -350,7 +395,10 @@ void main() {
}); });
test('should_do_nothing_when_notifying_after_dispose', () { test('should_do_nothing_when_notifying_after_dispose', () {
final logic = DiscoverLogic(history: FakeWatchHistory(), drama: _drama('s')); final logic = DiscoverLogic(
history: FakeWatchHistory(),
drama: _drama('s'),
);
var notifications = 0; var notifications = 0;
logic.addListener(() => notifications++); logic.addListener(() => notifications++);
+21 -16
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/models/drama.dart';
@@ -21,23 +21,28 @@ Drama _drama(String id, {String language = 'id'}) => Drama(
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
test('should_query_backend_and_render_results_when_search_text_changes', () async { test(
final repository = _FakeSearchRepository(results: [_drama('velvet')]); 'should_query_backend_and_render_results_when_search_text_changes',
final logic = DiscoverLogic( () async {
history: FakeWatchHistory(), final repository = _FakeSearchRepository(results: [_drama('velvet')]);
drama: _drama('signal'), final logic = DiscoverLogic(
searchRepository: repository, history: FakeWatchHistory(),
)..searchDebounce = Duration.zero; drama: _drama('signal'),
searchRepository: repository,
)..searchDebounce = Duration.zero;
logic.search.text = 'velvet'; logic.search.text = 'velvet';
logic.onSearchChanged(); logic.onSearchChanged();
await pumpEventQueue(); await pumpEventQueue();
expect(repository.calls, [(keyword: 'velvet', pageIndex: 1, pageSize: 20)]); expect(repository.calls, [
expect(logic.searchLoading, isFalse); (keyword: 'velvet', pageIndex: 1, pageSize: 20),
expect(logic.visibleDramas(const []).single.id, 'velvet'); ]);
logic.dispose(); expect(logic.searchLoading, isFalse);
}); expect(logic.visibleDramas(const []).single.id, 'velvet');
logic.dispose();
},
);
test('should_keep_server_results_when_text_only_loses_whitespace', () async { test('should_keep_server_results_when_text_only_loses_whitespace', () async {
final repository = _FakeSearchRepository(results: [_drama('velvet')]); final repository = _FakeSearchRepository(results: [_drama('velvet')]);
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/play_ticket.dart'; import 'package:fruitshorts/core/data/models/play_ticket.dart';
+1 -6
View File
@@ -28,12 +28,7 @@ void main() {
), ),
); );
logic.sessionEpisodes = [ logic.sessionEpisodes = [
Episode( Episode(id: '12', number: 1, title: 'Episode 1', isFree: true),
id: '12',
number: 1,
title: 'Episode 1',
isFree: true,
),
]; ];
final ready = await logic.ensureTicket(0); final ready = await logic.ensureTicket(0);
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
+1 -1
View File
@@ -1,4 +1,4 @@
// Author: fruit.com.cn // Author: fruit.com.cn
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
+1 -1
View File
@@ -1,4 +1,4 @@
import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/data/models/home_feed.dart'; import 'package:fruitshorts/core/data/models/home_feed.dart';
import 'package:fruitshorts/core/data/models/search_result.dart'; import 'package:fruitshorts/core/data/models/search_result.dart';
import 'package:fruitshorts/core/data/repositories/catalog_repository.dart'; import 'package:fruitshorts/core/data/repositories/catalog_repository.dart';