feat(app): 客户端展示多语言并支持筛选分页
This commit is contained in:
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];
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user