feat(app): 客户端展示多语言并支持筛选分页

This commit is contained in:
Zhou Weidong committed 2026-10-24 17:20:24 +08:00
1 parent 8481611372
commit 8b50b5bd06
68 files changed
+2742 -1120

No files matched your search

@@ -0,0 +1,128 @@
// Author: fruit.com.cn
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/content_category.dart';
import 'package:fruitshorts/core/data/models/drama.dart';
import 'package:fruitshorts/core/data/models/page_result.dart';
import 'package:fruitshorts/core/data/services/all_dramas_controller.dart';
void main() {
test('should_replace_first_page_when_refresh_succeeds', () async {
final remote = _ScriptedRemote(
pages: [
PageResult(total: 2, list: [_drama('a'), _drama('b')]),
],
);
final controller = AllDramasController(api: remote);
await controller.refresh();
expect(controller.dramas.map((item) => item.id), ['a', 'b']);
expect(controller.hasMore, isFalse);
expect(controller.error, isNull);
expect(remote.pagesRequested, [1]);
});
test('should_append_next_page_when_more_dramas_remain', () async {
final remote = _ScriptedRemote(
pages: [
PageResult(total: 3, list: [_drama('a'), _drama('b')]),
PageResult(total: 3, list: [_drama('c')]),
],
);
final controller = AllDramasController(api: remote);
await controller.refresh();
await controller.loadMore();
expect(controller.dramas.map((item) => item.id), ['a', 'b', 'c']);
expect(controller.hasMore, isFalse);
expect(remote.pagesRequested, [1, 2]);
});
test('should_keep_loaded_dramas_when_next_page_fails', () async {
final remote = _ScriptedRemote(
pages: [PageResult(total: 3, list: [_drama('a')])],
failOnPage: 2,
);
final controller = AllDramasController(api: remote);
await controller.refresh();
await controller.loadMore();
expect(controller.dramas.map((item) => item.id), ['a']);
expect(controller.error, isNotNull);
});
test('should_clear_selected_type_when_all_is_chosen', () {
final controller = AllDramasController.preview(
categories: const [ContentCategory(id: '2', name: '甜宠')],
);
controller.toggleCategory('2');
controller.toggleCategory('');
expect(controller.selectedCategories, isEmpty);
});
test('should_send_selected_ids_when_loading_a_page', () async {
final remote = _ScriptedRemote(
pages: [PageResult(total: 0, list: const [])],
);
final controller = AllDramasController(api: remote);
controller.toggleCategory('2');
controller.toggleClassification('3');
controller.toggleTag('4');
await controller.refresh();
expect(remote.lastCategoryIds, ['2']);
expect(remote.lastClassificationIds, ['3']);
expect(remote.lastTagIds, ['4']);
});
}
Drama _drama(String id) {
return Drama(id: id, title: id, synopsis: '', posterUrl: '');
}
class _ScriptedRemote implements AllDramasRemote {
_ScriptedRemote({required this.pages, this.failOnPage});
final List<PageResult<Drama>> pages;
final int? failOnPage;
final List<int> pagesRequested = [];
List<String> lastCategoryIds = const [];
List<String> lastClassificationIds = const [];
List<String> lastTagIds = const [];
@override
Future<List<ContentCategory>> classifications() async => const [
ContentCategory(id: '3', name: '精选'),
];
@override
Future<List<ContentCategory>> categories() async => const [
ContentCategory(id: '2', name: '甜宠'),
];
@override
Future<List<ContentCategory>> tags() async => const [
ContentCategory(id: '4', name: '热剧'),
];
@override
Future<PageResult<Drama>> dramas({
required List<String> categoryIds,
required List<String> classificationIds,
required List<String> tagIds,
required int pageIndex,
}) async {
lastCategoryIds = categoryIds;
lastClassificationIds = classificationIds;
lastTagIds = tagIds;
pagesRequested.add(pageIndex);
if (pageIndex == failOnPage) throw StateError('page failed');
return pages[pageIndex - 1];
}
}
+65
View File
@@ -0,0 +1,65 @@
// Author: fruit.com.cn
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/page_result.dart';
import 'package:fruitshorts/core/data/models/playlist_detail.dart';
import 'package:fruitshorts/core/data/repositories/playlist_repository.dart';
import 'package:fruitshorts/core/data/services/playlist_controller.dart';
void main() {
test('should_append_the_next_page_when_more_playlists_remain', () async {
final repository = _PagedPlaylists(total: 12);
final controller = PlaylistController(repository);
await controller.load();
expect(controller.items, hasLength(PlaylistController.pageSize));
expect(controller.hasMore, isTrue);
await controller.loadMore();
expect(controller.items, hasLength(12));
expect(controller.items.last.id, '11');
expect(controller.hasMore, isFalse);
controller.dispose();
});
}
class _PagedPlaylists implements PlaylistRepository {
_PagedPlaylists({required this.total});
final int total;
@override
Future<PageResult<PlaylistDetail>> fetchPlaylists({
int pageIndex = 1,
int pageSize = 10,
}) async {
final start = (pageIndex - 1) * pageSize;
if (start >= total) {
return PageResult(total: total);
}
final end = start + pageSize > total ? total : start + pageSize;
return PageResult(
total: total,
list: [
for (var index = start; index < end; index++)
PlaylistDetail(
id: '$index',
name: '剧单$index',
description: '',
dramaCount: 0,
dramas: const [],
),
],
);
}
@override
Future<PlaylistDetail> fetchPlaylist(
String id, {
int pageIndex = 1,
int pageSize = 20,
}) {
throw UnimplementedError();
}
}
+42
View File
@@ -0,0 +1,42 @@
// Author: fruit.com.cn
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/playlist_detail.dart';
void main() {
test('should_parse_playlist_cards_when_detail_json_arrives', () {
final playlist = PlaylistDetail.fromJson({
'id': '2112807182243336196',
'name': '本周必看',
'locales': {'zh': '本周必看', 'en': 'Weekly Hits', 'id': 'Hits Mingguan'},
'description': '编辑精选',
'descriptionLocales': {
'zh': '编辑精选',
'en': 'Editor picks',
'id': 'Pilihan editor',
},
'dramaCount': 1,
'list': [
{
'id': '5',
'title': '命运的相遇',
'cover': '/files/cover.jpg',
'synopsis': '一次意外',
'totalEpisodes': 12,
'viewCount': '20',
},
],
});
expect(playlist.id, '2112807182243336196');
expect(playlist.name, '本周必看');
expect(playlist.description, '编辑精选');
expect(playlist.localizedName('en'), 'Weekly Hits');
expect(playlist.localizedDescription('id'), 'Pilihan editor');
expect(playlist.localizedName('zh'), '本周必看');
expect(playlist.dramaCount, 1);
expect(playlist.dramas.single.title, '命运的相遇');
expect(playlist.dramas.single.posterUrl, '/files/cover.jpg');
expect(playlist.dramas.single.totalEpisodes, 12);
});
}