feat(flutter): 接入分类记录收藏与下载授权

This commit is contained in:
Zhou Weidong committed 2026-10-17 14:49:16 +08:00
1 parent 00280ca0da
commit cc4d7c5a9d
52 files changed
+4440 -171

No files matched your search

+20 -5
View File
@@ -77,18 +77,33 @@ void main() {
final repository = InMemoryFavoriteRepository(
saved: {'signal'},
cards: [_card('signal')],
);
)..failRemove = true;
final controller = FavoriteController(repository);
await controller.load();
final ok = await controller.remove('signal');
// InMemory 实现的 remove 不会失败,这里断言正常路径:状态与列表一起清掉
expect(ok, isTrue);
expect(controller.isSaved('signal'), isFalse);
expect(controller.cards, isEmpty);
expect(ok, isFalse);
expect(controller.isSaved('signal'), isTrue);
expect(controller.cards.map((drama) => drama.id), ['signal']);
});
test(
'should_restore_removed_card_at_original_position_when_remove_fails',
() async {
final repository = InMemoryFavoriteRepository(
saved: {'signal', 'city'},
cards: [_card('signal'), _card('city')],
)..failRemove = true;
final controller = FavoriteController(repository);
await controller.load();
await controller.remove('signal');
expect(controller.cards.map((drama) => drama.id), ['signal', 'city']);
},
);
test('should_clear_state_when_signing_out', () async {
final controller = FavoriteController(
InMemoryFavoriteRepository(saved: {'signal'}, cards: [_card('signal')]),
+153
View File
@@ -0,0 +1,153 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/data/remote/api_client.dart';
import 'package:fruitshorts/core/data/remote/auth_api.dart';
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
import 'package:fruitshorts/core/data/services/auth_controller.dart';
import 'package:fruitshorts/core/data/services/auth_session_store.dart';
import 'package:fruitshorts/core/data/services/favorite_controller.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/favorites/favorites_page.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 '../support/fake_http_adapter.dart';
Drama _card(String id) => Drama(
id: id,
title: 'Drama $id',
synopsis: 'synopsis',
posterUrl: '/files/$id.png',
totalEpisodes: 12,
);
void main() {
setUp(() {
SharedPreferencesAsyncPlatform.instance =
InMemorySharedPreferencesAsync.empty();
});
testWidgets('should_render_favorite_cards_and_visible_remove_action', (
tester,
) async {
final auth = await _auth(signedIn: true);
final favorites = FavoriteController(
InMemoryFavoriteRepository(
saved: {'signal', 'city'},
cards: [_card('signal'), _card('city')],
),
);
await _pumpPage(tester, auth: auth, favorites: favorites);
expect(find.text('Drama signal'), findsOneWidget);
expect(find.text('Drama city'), findsOneWidget);
expect(find.byKey(const Key('favorite-remove-signal')), findsOneWidget);
});
testWidgets('should_keep_card_and_show_failure_when_remove_fails', (
tester,
) async {
final auth = await _auth(signedIn: true);
final repository = InMemoryFavoriteRepository(
saved: {'signal'},
cards: [_card('signal')],
)..failRemove = true;
final favorites = FavoriteController(repository);
await _pumpPage(tester, auth: auth, favorites: favorites);
await tester.tap(find.byKey(const Key('favorite-remove-signal')));
await tester.pumpAndSettle();
expect(find.text('Drama signal'), findsOneWidget);
expect(
find.text('Could not update favorites. Please retry.'),
findsOneWidget,
);
});
testWidgets('should_show_empty_state_when_favorite_list_is_empty', (
tester,
) async {
final auth = await _auth(signedIn: true);
final favorites = FavoriteController(InMemoryFavoriteRepository());
await _pumpPage(tester, auth: auth, favorites: favorites);
expect(find.byKey(const Key('favorites-empty')), findsOneWidget);
});
testWidgets('should_show_retry_when_favorite_list_fails', (tester) async {
final auth = await _auth(signedIn: true);
final repository = InMemoryFavoriteRepository()..failList = true;
final favorites = FavoriteController(repository);
await _pumpPage(tester, auth: auth, favorites: favorites);
expect(find.byKey(const Key('favorites-error')), findsOneWidget);
expect(find.byKey(const Key('favorites-retry')), findsOneWidget);
});
testWidgets(
'should_show_auth_gate_without_loading_favorites_when_signed_out',
(tester) async {
final auth = await _auth(signedIn: false);
final repository = InMemoryFavoriteRepository(
saved: {'signal'},
cards: [_card('signal')],
);
final favorites = FavoriteController(repository);
await _pumpPage(tester, auth: auth, favorites: favorites);
expect(find.byKey(const Key('auth-gate-panel')), findsOneWidget);
expect(repository.listCalls, 0);
},
);
}
Future<AuthController> _auth({required bool signedIn}) async {
if (signedIn) {
SharedPreferencesAsyncPlatform.instance =
InMemorySharedPreferencesAsync.withData({
'auth.accessToken': 'test-access',
'auth.refreshToken': 'test-refresh',
'auth.user': '{"uid":"1","nickname":"Viewer","avatarUrl":""}',
});
}
final client = ApiClient(
baseUrl: 'http://127.0.0.1:8081',
adapter: FakeHttpAdapter(
(_) async => FakeHttpResponse('{"code":0,"message":"ok","data":null}'),
),
);
final auth = AuthController(AuthApi(client), AuthSessionStore());
await auth.load();
return auth;
}
Future<void> _pumpPage(
WidgetTester tester, {
required AuthController auth,
required FavoriteController favorites,
}) async {
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider<AuthController>.value(value: auth),
ChangeNotifierProvider<FavoriteController>.value(value: favorites),
],
child: MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const FavoritesPage(),
),
),
);
await tester.pumpAndSettle();
}