fix(mine): 支持未登录展示互动计数
This commit is contained in:
1 parent
06a2f6d7c0
commit
8d511933df
4 files changed
+334
-19
No files matched your search
@@ -60,6 +60,20 @@ void main() {
|
||||
logic.dispose();
|
||||
});
|
||||
|
||||
test('should_clear_recent_when_identity_is_reset', () async {
|
||||
final history = FakeWatchHistory()..recentRecords = const [_record];
|
||||
final logic = MineLogic(history: history);
|
||||
await pumpEventQueue();
|
||||
|
||||
logic.resetRecent();
|
||||
|
||||
expect(logic.recent, isEmpty);
|
||||
expect(logic.watchRecordTotal, isNull);
|
||||
expect(logic.recentLoading, isFalse);
|
||||
expect(logic.recentError, isNull);
|
||||
logic.dispose();
|
||||
});
|
||||
|
||||
testWidgets('should_forward_action_when_embedded_or_pop_when_standalone', (
|
||||
tester,
|
||||
) async {
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
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/page_result.dart';
|
||||
import 'package:fruitshorts/core/data/models/wallet.dart';
|
||||
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/commerce_repository.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/data/services/auth_controller.dart';
|
||||
import 'package:fruitshorts/core/theme/app_colors.dart';
|
||||
import 'package:fruitshorts/core/theme/app_dimens.dart';
|
||||
import 'package:fruitshorts/core/theme/app_theme.dart';
|
||||
@@ -18,6 +21,8 @@ import 'package:fruitshorts/pages/mine/about_page.dart';
|
||||
import 'package:fruitshorts/pages/mine/mine_types.dart';
|
||||
import 'package:fruitshorts/pages/mine/widgets/mine_menu.dart';
|
||||
import 'package:fruitshorts/pages/store/vip_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.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';
|
||||
|
||||
@@ -67,7 +72,9 @@ void main() {
|
||||
expect(find.byKey(const Key('mine-vip-open')), findsOneWidget);
|
||||
expect(find.byKey(const Key('mine-continue-watching')), findsNothing);
|
||||
expect(find.byKey(const Key('mine-stats')), findsOneWidget);
|
||||
expect(find.text('—'), findsNWidgets(3));
|
||||
expect(_statValue(tester, const Key('mine-stat-following')), '3');
|
||||
expect(_statValue(tester, const Key('mine-stat-watch-records')), '0');
|
||||
expect(_statValue(tester, const Key('mine-stat-coins')), '—');
|
||||
expect(find.byKey(const Key('mine-menu')), findsOneWidget);
|
||||
expect(find.byKey(const Key('mine-more-services')), findsOneWidget);
|
||||
expect(find.byKey(const Key('mine-mini-player')), findsNothing);
|
||||
@@ -242,6 +249,99 @@ void main() {
|
||||
expect(find.byType(AuthPage), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_show_device_counts_when_signed_out', (tester) async {
|
||||
await _pumpApp(
|
||||
tester,
|
||||
signedIn: false,
|
||||
history: InMemoryWatchHistoryRepository(seed: [_recent]),
|
||||
);
|
||||
await _openMine(tester);
|
||||
|
||||
expect(_statValue(tester, const Key('mine-stat-following')), '3');
|
||||
expect(_statValue(tester, const Key('mine-stat-watch-records')), '1');
|
||||
expect(_statValue(tester, const Key('mine-stat-coins')), '—');
|
||||
expect(find.byKey(const Key('mine-continue-watching')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_switch_mine_counts_when_login_identity_changes', (
|
||||
tester,
|
||||
) async {
|
||||
final catalog = FakeCatalogRepository();
|
||||
final favorites = _IdentityMineFavoriteRepository(
|
||||
device: catalog.dramas.take(2).toList(),
|
||||
account: [catalog.dramas[2]],
|
||||
);
|
||||
final history = _IdentityMineWatchHistoryRepository(
|
||||
device: [_recent, _secondRecent],
|
||||
account: [_accountRecent],
|
||||
);
|
||||
await _pumpApp(
|
||||
tester,
|
||||
signedIn: false,
|
||||
favorite: favorites,
|
||||
history: history,
|
||||
);
|
||||
await _openMine(tester);
|
||||
|
||||
expect(_statValue(tester, const Key('mine-stat-following')), '2');
|
||||
expect(_statValue(tester, const Key('mine-stat-watch-records')), '2');
|
||||
expect(_statValue(tester, const Key('mine-stat-coins')), '—');
|
||||
|
||||
final auth = Provider.of<AuthController>(
|
||||
tester.element(find.byKey(const Key('mine-page-surface'))),
|
||||
listen: false,
|
||||
);
|
||||
final preferences = SharedPreferencesAsync();
|
||||
favorites.scope = 'account';
|
||||
history.scope = 'account';
|
||||
await _setSignedIn(preferences);
|
||||
await auth.load();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(auth.isSignedIn, isTrue);
|
||||
expect(_statValue(tester, const Key('mine-stat-following')), '1');
|
||||
expect(_statValue(tester, const Key('mine-stat-watch-records')), '1');
|
||||
expect(_statValue(tester, const Key('mine-stat-coins')), '320');
|
||||
expect(find.text('Account history'), findsOneWidget);
|
||||
expect(find.text('Device history'), findsNothing);
|
||||
|
||||
favorites.scope = 'device';
|
||||
history.scope = 'device';
|
||||
await _clearSignedIn(preferences);
|
||||
await auth.load();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(auth.isSignedIn, isFalse);
|
||||
expect(_statValue(tester, const Key('mine-stat-following')), '2');
|
||||
expect(_statValue(tester, const Key('mine-stat-watch-records')), '2');
|
||||
expect(_statValue(tester, const Key('mine-stat-coins')), '—');
|
||||
expect(find.text('Device history'), findsOneWidget);
|
||||
expect(find.text('Account history'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_refresh_watch_count_after_history_is_cleared', (
|
||||
tester,
|
||||
) async {
|
||||
final history = InMemoryWatchHistoryRepository(seed: [_recent]);
|
||||
await _pumpApp(tester, signedIn: false, history: history);
|
||||
await _openMine(tester);
|
||||
|
||||
expect(_statValue(tester, const Key('mine-stat-watch-records')), '1');
|
||||
|
||||
await tester.tap(find.byKey(const Key('mine-history')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('watch-records-clear')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Clear'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('watch-records-empty')), findsOneWidget);
|
||||
|
||||
await tester.binding.handlePopRoute();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(_statValue(tester, const Key('mine-stat-watch-records')), '0');
|
||||
});
|
||||
|
||||
testWidgets('should_render_signed_in_user_without_floating_mini_player', (
|
||||
tester,
|
||||
) async {
|
||||
@@ -414,6 +514,34 @@ const _recent = WatchProgress(
|
||||
duration: Duration(minutes: 2),
|
||||
);
|
||||
|
||||
const _secondRecent = WatchProgress(
|
||||
dramaId: 'vengeance-demo',
|
||||
episodeId: 'vengeance-demo-1',
|
||||
dramaTitle: 'Device history',
|
||||
episodeNumber: 1,
|
||||
totalEpisodes: 6,
|
||||
position: Duration(seconds: 15),
|
||||
duration: Duration(minutes: 2),
|
||||
);
|
||||
|
||||
const _accountRecent = WatchProgress(
|
||||
dramaId: 'the-signal-demo',
|
||||
episodeId: 'the-signal-demo-2',
|
||||
dramaTitle: 'Account history',
|
||||
episodeNumber: 2,
|
||||
totalEpisodes: 7,
|
||||
position: Duration(seconds: 20),
|
||||
duration: Duration(minutes: 2),
|
||||
);
|
||||
|
||||
String _statValue(WidgetTester tester, Key key) {
|
||||
return tester
|
||||
.widget<Text>(
|
||||
find.descendant(of: find.byKey(key), matching: find.byType(Text)).first,
|
||||
)
|
||||
.data!;
|
||||
}
|
||||
|
||||
Future<void> _openMine(WidgetTester tester) async {
|
||||
await tester.tap(find.byKey(const Key('tab-mine')));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -423,6 +551,7 @@ Future<void> _pumpApp(
|
||||
WidgetTester tester, {
|
||||
required bool signedIn,
|
||||
WatchHistoryRepository? history,
|
||||
FavoriteRepository? favorite,
|
||||
CommerceRepository? commerce,
|
||||
}) async {
|
||||
tester.view.physicalSize = const Size(390, 1000);
|
||||
@@ -443,10 +572,12 @@ Future<void> _pumpApp(
|
||||
catalogRepository: catalog,
|
||||
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
||||
watchHistoryRepository: history ?? InMemoryWatchHistoryRepository(),
|
||||
favoriteRepository: InMemoryFavoriteRepository(
|
||||
saved: catalog.dramas.map((drama) => drama.id).toSet(),
|
||||
cards: catalog.dramas,
|
||||
),
|
||||
favoriteRepository:
|
||||
favorite ??
|
||||
InMemoryFavoriteRepository(
|
||||
saved: catalog.dramas.map((drama) => drama.id).toSet(),
|
||||
cards: catalog.dramas,
|
||||
),
|
||||
playbackRepository: FakePlaybackRepository(),
|
||||
commentRepository: FakeCommentRepository(),
|
||||
interactionRepository: FakeInteractionRepository(),
|
||||
@@ -457,6 +588,105 @@ Future<void> _pumpApp(
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> _setSignedIn(SharedPreferencesAsync preferences) async {
|
||||
await preferences.setString('auth.accessToken', 'access');
|
||||
await preferences.setString('auth.refreshToken', 'refresh');
|
||||
await preferences.setString(
|
||||
'auth.user',
|
||||
'{"uid":"U-100","nickname":"Viewer","avatarUrl":""}',
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _clearSignedIn(SharedPreferencesAsync preferences) async {
|
||||
await preferences.remove('auth.accessToken');
|
||||
await preferences.remove('auth.refreshToken');
|
||||
await preferences.remove('auth.user');
|
||||
}
|
||||
|
||||
class _IdentityMineFavoriteRepository implements FavoriteRepository {
|
||||
_IdentityMineFavoriteRepository({
|
||||
required List<Drama> device,
|
||||
required List<Drama> account,
|
||||
}) : _cards = {'device': device, 'account': account};
|
||||
|
||||
final Map<String, List<Drama>> _cards;
|
||||
String scope = 'device';
|
||||
|
||||
@override
|
||||
Future<PageResult<Drama>> page({int pageIndex = 1, int pageSize = 20}) async {
|
||||
final cards = _cards[scope]!;
|
||||
final offset = (pageIndex - 1) * pageSize;
|
||||
return PageResult(
|
||||
total: cards.length,
|
||||
list: offset >= cards.length
|
||||
? const []
|
||||
: cards.skip(offset).take(pageSize).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String dramaId) async {
|
||||
_cards[scope]!.removeWhere((drama) => drama.id == dramaId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeMany(List<String> dramaIds) async {
|
||||
final ids = dramaIds.toSet();
|
||||
_cards[scope]!.removeWhere((drama) => ids.contains(drama.id));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> toggle(String dramaId) async => true;
|
||||
}
|
||||
|
||||
class _IdentityMineWatchHistoryRepository implements WatchHistoryRepository {
|
||||
_IdentityMineWatchHistoryRepository({
|
||||
required List<WatchProgress> device,
|
||||
required List<WatchProgress> account,
|
||||
}) : _records = {'device': device, 'account': account};
|
||||
|
||||
final Map<String, List<WatchProgress>> _records;
|
||||
String scope = 'device';
|
||||
|
||||
@override
|
||||
Future<void> clear() async => _records[scope]!.clear();
|
||||
|
||||
@override
|
||||
Future<WatchProgress?> progressFor(String dramaId) async {
|
||||
return _records[scope]!
|
||||
.where((record) => record.dramaId == dramaId)
|
||||
.firstOrNull;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PageResult<WatchProgress>> recent({
|
||||
int pageIndex = 1,
|
||||
int pageSize = 20,
|
||||
}) async {
|
||||
final records = _records[scope]!;
|
||||
final offset = (pageIndex - 1) * pageSize;
|
||||
return PageResult(
|
||||
total: records.length,
|
||||
list: offset >= records.length
|
||||
? const []
|
||||
: records.skip(offset).take(pageSize).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String dramaId) async {
|
||||
_records[scope]!.removeWhere((record) => record.dramaId == dramaId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WatchProgress?> save({
|
||||
required String episodeId,
|
||||
required Duration position,
|
||||
Duration? duration,
|
||||
bool finished = false,
|
||||
}) async => null;
|
||||
}
|
||||
|
||||
class _MineWalletCommerceRepository implements CommerceRepository {
|
||||
const _MineWalletCommerceRepository();
|
||||
|
||||
|
||||
Reference in new issue
Block a user