436 lines
15 KiB
Dart
436 lines
15 KiB
Dart
// 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/models/drama.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:fruitshorts/core/theme/app_dimens.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/widget_player.dart';
|
|
|
|
void main() {
|
|
testWidgets('should_render_following_tabs_grid_and_edit_actions', (
|
|
tester,
|
|
) async {
|
|
_signIn();
|
|
final catalog = FakeCatalogRepository();
|
|
await tester.pumpWidget(
|
|
_app(
|
|
catalog: catalog,
|
|
favorites: InMemoryFavoriteRepository(
|
|
cards: catalog.dramas.take(1).toList(),
|
|
saved: {catalog.dramas.first.id},
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('following-page')), findsOneWidget);
|
|
expect(find.byKey(const Key('following-tab-favorites')), findsOneWidget);
|
|
expect(find.byKey(const Key('following-tab-history')), findsOneWidget);
|
|
expect(find.byKey(const Key('following-tab-reserve')), findsNothing);
|
|
expect(find.byKey(const Key('following-favorites-grid')), findsOneWidget);
|
|
expect(find.byKey(const Key('following-add-card')), findsOneWidget);
|
|
expect(find.byType(CustomScrollView), findsWidgets);
|
|
final tab = tester.widget<AnimatedDefaultTextStyle>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('following-tab-favorites')),
|
|
matching: find.byType(AnimatedDefaultTextStyle),
|
|
),
|
|
);
|
|
expect(tab.style.fontSize, AppDimens.followingTabFont);
|
|
expect(tab.style.fontWeight, FontWeight.w600);
|
|
expect(tab.style.color, AppColors.textPrimary);
|
|
final editIcon = tester.widget<Icon>(find.byIcon(Icons.edit_outlined));
|
|
expect(editIcon.size, AppDimens.followingEditIconSize);
|
|
expect(editIcon.color, AppColors.textTabInactive);
|
|
final grid = tester.widget<SliverGrid>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('following-favorites-grid')),
|
|
matching: find.byType(SliverGrid),
|
|
),
|
|
);
|
|
final delegate =
|
|
grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
|
|
expect(delegate.crossAxisCount, 3);
|
|
expect(delegate.crossAxisSpacing, AppDimens.followingGridGap);
|
|
expect(delegate.mainAxisSpacing, AppDimens.followingGridRowGap);
|
|
expect(delegate.mainAxisExtent, greaterThan(0));
|
|
final card = find.byKey(const Key('following-card-the-signal-demo'));
|
|
final coverFinder = find.byKey(
|
|
const Key('following-cover-the-signal-demo'),
|
|
);
|
|
final cover = tester.widget<AspectRatio>(coverFinder);
|
|
expect(cover.aspectRatio, AppDimens.cardCoverAspectRatio);
|
|
final coverClip = tester.widget<ClipRRect>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('following-cover-the-signal-demo')),
|
|
matching: find.byType(ClipRRect),
|
|
),
|
|
);
|
|
expect(
|
|
coverClip.borderRadius,
|
|
BorderRadius.circular(AppDimens.homeCardRadius),
|
|
);
|
|
final titleFinder = find.descendant(
|
|
of: card,
|
|
matching: find.text('The Signal'),
|
|
);
|
|
final title = tester.widget<Text>(titleFinder);
|
|
expect(title.style?.fontSize, AppDimens.cardTitleFont);
|
|
expect(title.style?.fontWeight, FontWeight.w500);
|
|
expect(title.style?.height, AppDimens.cardTitleLineHeight);
|
|
final views = tester.widget<Text>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('following-views-the-signal-demo')),
|
|
matching: find.byType(Text),
|
|
),
|
|
);
|
|
expect(views.style?.fontSize, AppDimens.cardPlayCountFont);
|
|
expect(views.style?.fontWeight, FontWeight.w400);
|
|
expect(
|
|
find.descendant(
|
|
of: coverFinder,
|
|
matching: find.byKey(const Key('following-views-the-signal-demo')),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(views.style?.color, AppColors.textPrimary);
|
|
expect(
|
|
find.descendant(
|
|
of: find.byKey(const Key('following-views-the-signal-demo')),
|
|
matching: find.byIcon(Icons.local_fire_department_rounded),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
tester.getTopLeft(titleFinder).dx,
|
|
tester.getTopLeft(coverFinder).dx,
|
|
);
|
|
expect(
|
|
tester.getTopLeft(card).dx,
|
|
lessThan(
|
|
tester.getTopLeft(find.byKey(const Key('following-add-card'))).dx,
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byKey(const Key('following-edit')));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.byKey(const Key('following-select-the-signal-demo')),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.byKey(const Key('following-cancel-follow')), findsOneWidget);
|
|
|
|
await tester.tap(find.byKey(const Key('following-card-the-signal-demo')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('following-cancel-follow')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Unfollow the selected dramas?'), findsOneWidget);
|
|
await tester.tap(
|
|
find.descendant(
|
|
of: find.byType(AlertDialog),
|
|
matching: find.byType(FilledButton),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
find.byKey(const Key('following-select-the-signal-demo')),
|
|
findsNothing,
|
|
);
|
|
expect(find.byIcon(Icons.edit_outlined), findsOneWidget);
|
|
expect(
|
|
find.byKey(const Key('following-card-the-signal-demo')),
|
|
findsNothing,
|
|
);
|
|
|
|
await tester.tap(find.byKey(const Key('following-tab-history')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('following-history-empty')), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should_unfollow_every_selected_drama_when_edit_confirms', (
|
|
tester,
|
|
) async {
|
|
_signIn();
|
|
final catalog = FakeCatalogRepository();
|
|
final dramas = catalog.dramas.take(2).toList();
|
|
final favorites = InMemoryFavoriteRepository(
|
|
cards: dramas,
|
|
saved: dramas.map((drama) => drama.id).toSet(),
|
|
);
|
|
await tester.pumpWidget(_app(catalog: catalog, favorites: favorites));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('following-edit')));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('following-select-all')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('following-cancel-follow')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(
|
|
find.descendant(
|
|
of: find.byType(AlertDialog),
|
|
matching: find.byType(FilledButton),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
find.byKey(const Key('following-card-the-signal-demo')),
|
|
findsNothing,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('following-card-city-lights-demo')),
|
|
findsNothing,
|
|
);
|
|
expect(find.byIcon(Icons.edit_outlined), findsOneWidget);
|
|
expect(favorites.removeManyCalls, 1);
|
|
});
|
|
|
|
testWidgets('should_show_auth_gate_when_signed_out', (tester) async {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.empty();
|
|
await tester.pumpWidget(_app(catalog: FakeCatalogRepository()));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('auth-gate-panel')), findsOneWidget);
|
|
expect(find.byKey(const Key('following-favorites-grid')), findsNothing);
|
|
});
|
|
|
|
testWidgets('should_show_empty_favorites_and_history', (tester) async {
|
|
_signIn();
|
|
await tester.pumpWidget(_app(catalog: FakeCatalogRepository()));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('following-add-card')), findsOneWidget);
|
|
expect(find.byKey(const Key('following-favorites-empty')), findsNothing);
|
|
await tester.tap(find.byKey(const Key('following-tab-history')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('following-history-empty')), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should_render_history_metadata_without_detail_fetch', (
|
|
tester,
|
|
) async {
|
|
_signIn();
|
|
final catalog = _CountingCatalogRepository();
|
|
await tester.pumpWidget(
|
|
_app(
|
|
catalog: catalog,
|
|
history: InMemoryWatchHistoryRepository(
|
|
seed: [
|
|
WatchProgress(
|
|
dramaId: 'the-signal-demo',
|
|
episodeId: 'the-signal-demo-3',
|
|
dramaTitle: 'Server title',
|
|
dramaCover: '/files/server-cover.jpg',
|
|
episodeNumber: 3,
|
|
totalEpisodes: 8,
|
|
position: const Duration(seconds: 25),
|
|
duration: const Duration(seconds: 100),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('following-tab-history')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Server title'), findsOneWidget);
|
|
expect(find.text('Episode 3 / 8 total'), findsOneWidget);
|
|
expect(find.text('Watched 25%'), findsOneWidget);
|
|
expect(
|
|
find.byKey(const Key('following-history-card-the-signal-demo')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('following-history-continue-the-signal-demo')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('following-progress-the-signal-demo')),
|
|
findsOneWidget,
|
|
);
|
|
final cover = tester.widget<SizedBox>(
|
|
find.byKey(const Key('following-history-cover-the-signal-demo')),
|
|
);
|
|
expect(cover.width, AppDimens.followingHistoryCoverWidth);
|
|
expect(cover.height, AppDimens.followingHistoryCoverHeight);
|
|
final historyGrid = tester.widget<SliverGrid>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('following-history-grid')),
|
|
matching: find.byType(SliverGrid),
|
|
),
|
|
);
|
|
final historyDelegate =
|
|
historyGrid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
|
|
expect(historyDelegate.crossAxisCount, 1);
|
|
expect(historyDelegate.mainAxisSpacing, AppDimens.followingHistoryRowGap);
|
|
expect(catalog.detailFetches, 0);
|
|
|
|
await tester.tap(find.byKey(const Key('following-edit')));
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.byKey(const Key('following-history-remove-the-signal-demo')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.byKey(const Key('following-history-continue-the-signal-demo')),
|
|
findsNothing,
|
|
);
|
|
});
|
|
|
|
testWidgets('should_switch_following_pages_horizontally', (tester) async {
|
|
_signIn();
|
|
await tester.pumpWidget(_app(catalog: FakeCatalogRepository()));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.drag(
|
|
find.byKey(const Key('following-tab-pages')),
|
|
const Offset(-500, 0),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('following-history-empty')), findsOneWidget);
|
|
final historyTab = tester.widget<AnimatedDefaultTextStyle>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('following-tab-history')),
|
|
matching: find.byType(AnimatedDefaultTextStyle),
|
|
),
|
|
);
|
|
expect(historyTab.style.fontWeight, FontWeight.w600);
|
|
expect(historyTab.style.color, AppColors.textPrimary);
|
|
});
|
|
|
|
testWidgets('should_open_home_from_add_card', (tester) async {
|
|
_signIn();
|
|
await tester.pumpWidget(_app(catalog: FakeCatalogRepository()));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('following-add-card')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('home-grid')), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should_reset_edit_state_when_switching_tabs', (tester) async {
|
|
_signIn();
|
|
final catalog = FakeCatalogRepository();
|
|
await tester.pumpWidget(
|
|
_app(
|
|
catalog: catalog,
|
|
favorites: InMemoryFavoriteRepository(
|
|
cards: catalog.dramas,
|
|
saved: catalog.dramas.map((drama) => drama.id).toSet(),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('following-edit')));
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('following-tab-history')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('following-tab-favorites')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
find.byKey(const Key('following-card-the-signal-demo')),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.byIcon(Icons.edit_outlined), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should_fit_following_cards_on_compact_viewport', (tester) async {
|
|
tester.view.physicalSize = const Size(320, 640);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
_signIn();
|
|
final catalog = FakeCatalogRepository();
|
|
await tester.pumpWidget(
|
|
_app(
|
|
catalog: catalog,
|
|
favorites: InMemoryFavoriteRepository(
|
|
cards: catalog.dramas.take(1).toList(),
|
|
saved: {catalog.dramas.first.id},
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-follow')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
find.byKey(const Key('following-card-the-signal-demo')),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|
|
|
|
void _signIn() {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.withData({
|
|
'auth.accessToken': 'access',
|
|
'auth.refreshToken': 'refresh',
|
|
'auth.user': '{"uid":"9","nickname":"Viewer","avatarUrl":""}',
|
|
});
|
|
}
|
|
|
|
Widget _app({
|
|
required FakeCatalogRepository catalog,
|
|
FavoriteRepository? favorites,
|
|
WatchHistoryRepository? history,
|
|
}) {
|
|
return LumaDramaApp(
|
|
playerFactory: createWidgetPlayer,
|
|
catalogRepository: catalog,
|
|
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
|
watchHistoryRepository: history ?? InMemoryWatchHistoryRepository(),
|
|
favoriteRepository: favorites ?? InMemoryFavoriteRepository(),
|
|
playbackRepository: FakePlaybackRepository(),
|
|
commentRepository: FakeCommentRepository(),
|
|
interactionRepository: FakeInteractionRepository(),
|
|
);
|
|
}
|
|
|
|
class _CountingCatalogRepository extends FakeCatalogRepository {
|
|
int detailFetches = 0;
|
|
|
|
@override
|
|
Future<Drama> fetchDrama(String dramaId) {
|
|
detailFetches += 1;
|
|
return super.fetchDrama(dramaId);
|
|
}
|
|
}
|