feat(app): 剧场页九宫格与极简我的页
This commit is contained in:
1 parent
dc8ca7b743
commit
35338cbfc8
46 files changed
+2353
-4358
No files matched your search
+38
-201
@@ -1,173 +1,68 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/config/locale_controller.dart';
|
||||
import 'package:fruitshorts/core/data/models/drama.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
||||
import 'package:fruitshorts/pages/mine/mine_logic.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 'package:fruitshorts/pages/mine/mine_types.dart';
|
||||
|
||||
import '../support/fake_watch_history.dart';
|
||||
|
||||
Drama _drama(String id, {List<int> numbers = const [1, 2, 3]}) => Drama(
|
||||
id: id,
|
||||
title: id,
|
||||
synopsis: 'synopsis',
|
||||
posterUrl: '/files/poster.png',
|
||||
episodes: [
|
||||
for (final number in numbers)
|
||||
Episode(
|
||||
id: '$id-$number',
|
||||
number: number,
|
||||
title: 'Episode $number',
|
||||
videoUrl: 'https://example.test/clip.mp4',
|
||||
isFree: true,
|
||||
),
|
||||
],
|
||||
const _record = WatchProgress(
|
||||
dramaId: 'signal',
|
||||
episodeId: 'signal-1',
|
||||
position: Duration(seconds: 7),
|
||||
duration: Duration(minutes: 2),
|
||||
);
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
test(
|
||||
'should_keep_requested_section_and_read_episode_one_when_constructed',
|
||||
() async {
|
||||
final history = FakeWatchHistory()..saved = const Duration(seconds: 7);
|
||||
final logic = MineLogic(
|
||||
history: history,
|
||||
drama: _drama('signal'),
|
||||
initialSection: 1,
|
||||
);
|
||||
test('should_load_recent_watches_when_constructed', () async {
|
||||
final history = FakeWatchHistory()..recentRecords = const [_record];
|
||||
final logic = MineLogic(history: history);
|
||||
|
||||
expect(logic.section, 1);
|
||||
expect(logic.filter, 0);
|
||||
expect(await logic.position, const Duration(seconds: 7));
|
||||
expect(history.reads, ['signal']);
|
||||
logic.dispose();
|
||||
},
|
||||
);
|
||||
await pumpEventQueue();
|
||||
|
||||
test(
|
||||
'should_resume_from_last_watch_when_list_card_has_no_episodes',
|
||||
() async {
|
||||
final history = FakeWatchHistory()
|
||||
..saved = const Duration(seconds: 7)
|
||||
..lastEpisode = 'signal-3';
|
||||
final logic = MineLogic(
|
||||
history: history,
|
||||
drama: _drama('signal', numbers: const []),
|
||||
initialSection: 1,
|
||||
);
|
||||
|
||||
expect(await logic.position, const Duration(seconds: 7));
|
||||
expect(history.reads, ['signal']);
|
||||
logic.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
test('should_notify_when_section_and_filter_are_selected', () {
|
||||
final logic = MineLogic(
|
||||
history: FakeWatchHistory(),
|
||||
drama: _drama('s'),
|
||||
initialSection: 0,
|
||||
);
|
||||
var notifications = 0;
|
||||
logic.addListener(() => notifications++);
|
||||
|
||||
logic.selectSection(1);
|
||||
expect(logic.section, 1);
|
||||
expect(notifications, 1);
|
||||
|
||||
logic.selectFilter(2);
|
||||
expect(logic.filter, 2);
|
||||
expect(notifications, 2);
|
||||
expect(logic.recent.single.dramaId, 'signal');
|
||||
expect(logic.recentLoading, isFalse);
|
||||
expect(logic.recentError, isNull);
|
||||
logic.dispose();
|
||||
});
|
||||
|
||||
test('should_adopt_drama_only_when_page_is_active', () {
|
||||
final history = FakeWatchHistory();
|
||||
final logic = MineLogic(
|
||||
history: history,
|
||||
drama: _drama('signal'),
|
||||
initialSection: 0,
|
||||
);
|
||||
test('should_expose_error_when_recent_request_fails', () async {
|
||||
final history = FakeWatchHistory()..failReads = true;
|
||||
final logic = MineLogic(history: history);
|
||||
|
||||
void sync(
|
||||
Drama next, {
|
||||
required bool active,
|
||||
required bool wasActive,
|
||||
required bool dramaChanged,
|
||||
int sectionRequest = 0,
|
||||
int previousSectionRequest = 0,
|
||||
int initialSection = 0,
|
||||
}) => logic.syncPage(
|
||||
active: active,
|
||||
wasActive: wasActive,
|
||||
nextDrama: next,
|
||||
dramaChanged: dramaChanged,
|
||||
sectionRequest: sectionRequest,
|
||||
previousSectionRequest: previousSectionRequest,
|
||||
initialSection: initialSection,
|
||||
);
|
||||
await pumpEventQueue();
|
||||
|
||||
sync(_drama('city'), active: false, wasActive: true, dramaChanged: true);
|
||||
expect(logic.drama.id, 'signal');
|
||||
|
||||
sync(_drama('city'), active: true, wasActive: false, dramaChanged: false);
|
||||
expect(logic.drama.id, 'city');
|
||||
|
||||
history.reads.clear();
|
||||
sync(
|
||||
_drama('vengeance'),
|
||||
active: true,
|
||||
wasActive: true,
|
||||
dramaChanged: true,
|
||||
);
|
||||
expect(logic.drama.id, 'vengeance');
|
||||
expect(history.reads, ['vengeance']);
|
||||
|
||||
history.reads.clear();
|
||||
sync(_drama('other'), active: true, wasActive: true, dramaChanged: false);
|
||||
expect(logic.drama.id, 'vengeance');
|
||||
expect(history.reads, isEmpty);
|
||||
expect(logic.recent, isEmpty);
|
||||
expect(logic.recentError, 'progressError');
|
||||
logic.dispose();
|
||||
});
|
||||
|
||||
test(
|
||||
'should_reset_section_when_section_request_arrives_while_page_is_inactive',
|
||||
() {
|
||||
final logic = MineLogic(
|
||||
history: FakeWatchHistory(),
|
||||
drama: _drama('s'),
|
||||
initialSection: 0,
|
||||
);
|
||||
test('should_refresh_recent_only_when_page_becomes_active', () async {
|
||||
final history = FakeWatchHistory()..recentRecords = const [_record];
|
||||
final logic = MineLogic(history: history);
|
||||
await pumpEventQueue();
|
||||
|
||||
logic.syncPage(
|
||||
active: false,
|
||||
wasActive: false,
|
||||
nextDrama: logic.drama,
|
||||
dramaChanged: false,
|
||||
sectionRequest: 1,
|
||||
previousSectionRequest: 0,
|
||||
initialSection: 1,
|
||||
);
|
||||
history.recentRecords = const [];
|
||||
logic.syncPage(active: true, wasActive: true);
|
||||
await pumpEventQueue();
|
||||
expect(logic.recent, hasLength(1));
|
||||
|
||||
expect(logic.section, 1);
|
||||
logic.dispose();
|
||||
},
|
||||
);
|
||||
logic.syncPage(active: true, wasActive: false);
|
||||
await pumpEventQueue();
|
||||
expect(logic.recent, isEmpty);
|
||||
logic.dispose();
|
||||
});
|
||||
|
||||
testWidgets('should_return_choice_and_forward_it_when_act_runs_embedded', (
|
||||
testWidgets('should_forward_action_when_embedded_or_pop_when_standalone', (
|
||||
tester,
|
||||
) async {
|
||||
final logic = MineLogic(
|
||||
history: FakeWatchHistory(),
|
||||
drama: _drama('s'),
|
||||
initialSection: 0,
|
||||
);
|
||||
final logic = MineLogic(history: FakeWatchHistory());
|
||||
final forwarded = <MyPageAction>[];
|
||||
final popped = Completer<MyPageAction?>();
|
||||
late BuildContext pageContext;
|
||||
@@ -191,7 +86,6 @@ void main() {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('open'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
@@ -202,72 +96,15 @@ void main() {
|
||||
onAction: forwarded.add,
|
||||
);
|
||||
expect(forwarded, [MyPageAction.discover]);
|
||||
expect(Navigator.of(pageContext).canPop(), isTrue);
|
||||
|
||||
logic.act(pageContext, MyPageAction.resume, embedded: false);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await expectLater(popped.future, completion(MyPageAction.resume));
|
||||
logic.dispose();
|
||||
});
|
||||
|
||||
testWidgets('should_switch_app_locale_when_language_sheet_is_used', (
|
||||
tester,
|
||||
) async {
|
||||
SharedPreferencesAsyncPlatform.instance =
|
||||
InMemorySharedPreferencesAsync.empty();
|
||||
final locale = LocaleController();
|
||||
final logic = MineLogic(
|
||||
history: FakeWatchHistory(),
|
||||
drama: _drama('s'),
|
||||
initialSection: 0,
|
||||
);
|
||||
late BuildContext context;
|
||||
|
||||
await tester.pumpWidget(
|
||||
ChangeNotifierProvider<LocaleController>.value(
|
||||
value: locale,
|
||||
child: MaterialApp(
|
||||
locale: const Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Builder(
|
||||
builder: (inner) {
|
||||
context = inner;
|
||||
return const Scaffold(body: SizedBox.shrink());
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
unawaited(logic.showLanguage(context));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
expect(find.text(l10n.english), findsOneWidget);
|
||||
expect(find.text(l10n.simplifiedChinese), findsOneWidget);
|
||||
expect(find.text('Bahasa Indonesia'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Bahasa Indonesia'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(locale.locale, const Locale('id'));
|
||||
|
||||
unawaited(logic.showLanguage(context));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(l10n.simplifiedChinese));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(locale.locale, const Locale('zh'));
|
||||
logic.dispose();
|
||||
});
|
||||
|
||||
test('should_do_nothing_when_notifying_after_dispose', () {
|
||||
final logic = MineLogic(
|
||||
history: FakeWatchHistory(),
|
||||
drama: _drama('s'),
|
||||
initialSection: 0,
|
||||
);
|
||||
final logic = MineLogic(history: FakeWatchHistory());
|
||||
var notifications = 0;
|
||||
logic.addListener(() => notifications++);
|
||||
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.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 'package:fruitshorts/app.dart';
|
||||
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
||||
import 'package:fruitshorts/core/theme/app_colors.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_catalog.dart';
|
||||
import '../support/fake_category.dart';
|
||||
@@ -20,7 +18,7 @@ import '../support/fake_notice_repository.dart';
|
||||
import '../support/widget_player.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_render_me_user_wallet_continue_and_language_rows', (
|
||||
testWidgets('should_render_only_continue_and_six_core_entries', (
|
||||
tester,
|
||||
) async {
|
||||
SharedPreferencesAsyncPlatform.instance =
|
||||
@@ -59,58 +57,22 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('mine-user-row')), findsOneWidget);
|
||||
expect(find.byKey(const Key('mine-wallet')), findsOneWidget);
|
||||
for (final key in const [Key('mine-search-button'), Key('mine-avatar')]) {
|
||||
expect(tester.getSize(find.byKey(key)), const Size(36, 36));
|
||||
expect(
|
||||
tester
|
||||
.widget<Icon>(
|
||||
find.descendant(of: find.byKey(key), matching: find.byType(Icon)),
|
||||
)
|
||||
.size,
|
||||
18,
|
||||
);
|
||||
}
|
||||
expect(find.byKey(const Key('mine-continue-watching')), findsOneWidget);
|
||||
expect(find.byKey(const Key('mine-language')), findsOneWidget);
|
||||
expect(find.byKey(const Key('mine-library-tab-0')), findsOneWidget);
|
||||
expect(find.byKey(const Key('mine-library-tab-1')), findsOneWidget);
|
||||
expect(find.byKey(const Key('mine-library-tab-2')), findsOneWidget);
|
||||
final tab0 = find.byKey(const Key('mine-library-tab-0'));
|
||||
final tab1 = find.byKey(const Key('mine-library-tab-1'));
|
||||
final tab2 = find.byKey(const Key('mine-library-tab-2'));
|
||||
expect(tester.getTopLeft(tab0).dx, lessThan(tester.getTopLeft(tab1).dx));
|
||||
expect(tester.getTopLeft(tab1).dx, lessThan(tester.getTopLeft(tab2).dx));
|
||||
expect(
|
||||
tester.getTopLeft(tab1).dx - tester.getTopRight(tab0).dx,
|
||||
closeTo(20, 1),
|
||||
);
|
||||
expect(
|
||||
(tester
|
||||
.widget<AnimatedContainer>(
|
||||
find.descendant(
|
||||
of: tab0,
|
||||
matching: find.byType(AnimatedContainer),
|
||||
),
|
||||
)
|
||||
.decoration
|
||||
as BoxDecoration)
|
||||
.color,
|
||||
AppColors.blue,
|
||||
);
|
||||
expect(
|
||||
(tester
|
||||
.widget<AnimatedContainer>(
|
||||
find.descendant(
|
||||
of: tab1,
|
||||
matching: find.byType(AnimatedContainer),
|
||||
),
|
||||
)
|
||||
.decoration
|
||||
as BoxDecoration)
|
||||
.color,
|
||||
Colors.transparent,
|
||||
);
|
||||
expect(find.byKey(const Key('mine-continue-play')), findsOneWidget);
|
||||
for (final key in const [
|
||||
Key('mine-watchlist'),
|
||||
Key('mine-history'),
|
||||
Key('mine-downloads'),
|
||||
Key('mine-wallet'),
|
||||
Key('mine-vip'),
|
||||
Key('mine-settings'),
|
||||
]) {
|
||||
expect(find.byKey(key), findsOneWidget);
|
||||
}
|
||||
expect(find.byKey(const Key('mine-language')), findsNothing);
|
||||
expect(find.byKey(const Key('mine-library-tab-0')), findsNothing);
|
||||
expect(find.byKey(const Key('mine-library-tab-1')), findsNothing);
|
||||
expect(find.byKey(const Key('mine-library-tab-2')), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.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 'package:fruitshorts/core/config/locale_controller.dart';
|
||||
import 'package:fruitshorts/core/data/models/drama.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/mine/mine_logic.dart';
|
||||
|
||||
const _drama = Drama(
|
||||
id: 'drama-1',
|
||||
title: 'The Signal',
|
||||
synopsis: 'synopsis',
|
||||
posterUrl: '/files/1.jpg',
|
||||
);
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
SharedPreferencesAsyncPlatform.instance =
|
||||
InMemorySharedPreferencesAsync.empty();
|
||||
});
|
||||
|
||||
testWidgets('should_not_show_server_address_in_settings_sheet', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpSettings(tester);
|
||||
expect(find.byKey(const Key('server-endpoint-setting')), findsNothing);
|
||||
expect(find.text('Server address'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_keep_all_three_languages_in_settings_sheet', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpSettings(tester);
|
||||
|
||||
expect(find.text('English'), findsOneWidget);
|
||||
expect(find.text('简体中文'), findsOneWidget);
|
||||
expect(find.text('Bahasa Indonesia'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pumpSettings(WidgetTester tester) async {
|
||||
final logic = MineLogic(
|
||||
history: InMemoryWatchHistoryRepository(),
|
||||
drama: _drama,
|
||||
initialSection: 0,
|
||||
);
|
||||
addTearDown(logic.dispose);
|
||||
await tester.pumpWidget(
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => LocaleController(),
|
||||
child: MaterialApp(
|
||||
locale: const Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Builder(
|
||||
builder: (context) => Scaffold(
|
||||
body: TextButton(
|
||||
key: const Key('open-settings'),
|
||||
onPressed: () => logic.showLanguage(context),
|
||||
child: const Text('Open settings'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.byKey(const Key('open-settings')));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/app.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/watch_history_repository.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_catalog.dart';
|
||||
import '../support/fake_category.dart';
|
||||
import '../support/fake_interaction_repositories.dart';
|
||||
import '../support/fake_notice_repository.dart';
|
||||
import '../support/widget_player.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
SharedPreferencesAsyncPlatform.instance =
|
||||
InMemorySharedPreferencesAsync.empty();
|
||||
});
|
||||
|
||||
testWidgets('should_open_minimal_settings_without_language_switches', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(390, 844);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final catalog = FakeCatalogRepository();
|
||||
|
||||
await tester.pumpWidget(
|
||||
LumaDramaApp(
|
||||
playerFactory: createWidgetPlayer,
|
||||
catalogRepository: catalog,
|
||||
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
||||
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
||||
favoriteRepository: InMemoryFavoriteRepository(),
|
||||
playbackRepository: FakePlaybackRepository(),
|
||||
commentRepository: FakeCommentRepository(),
|
||||
interactionRepository: FakeInteractionRepository(),
|
||||
noticeRepository: FakeNoticeRepository(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('tab-my')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('mine-settings')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('settings-page')), findsOneWidget);
|
||||
expect(find.byKey(const Key('settings-notices')), findsOneWidget);
|
||||
expect(find.byKey(const Key('server-endpoint-setting')), findsNothing);
|
||||
expect(find.text('Server address'), findsNothing);
|
||||
expect(find.text('English'), findsNothing);
|
||||
expect(find.text('简体中文'), findsNothing);
|
||||
expect(find.text('Bahasa Indonesia'), findsNothing);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user