feat(flutter): 增加轮播自动播放与站内通知
This commit is contained in:
1 parent
b7b1c50427
commit
b870956665
24 files changed
+1398
-26
No files matched your search
@@ -1,25 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/discover/widgets/cover_carousel.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('should_turn_cube_and_keep_title_on_poster_when_carousel_pages', (tester) async {
|
||||
List<CoverItem> items(int count) => [
|
||||
for (var index = 0; index < count; index++)
|
||||
CoverItem(
|
||||
title: 'Banner $index',
|
||||
subtitle: 'Subtitle $index',
|
||||
image: '/files/banner-$index.png',
|
||||
onOpen: _noop,
|
||||
),
|
||||
];
|
||||
|
||||
testWidgets('should_turn_cube_and_keep_title_on_poster_when_carousel_pages', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: CoverCarousel(
|
||||
items: [
|
||||
for (final title in ['The Signal', 'City Lights', 'Vengeance'])
|
||||
CoverItem(
|
||||
title: title,
|
||||
subtitle: 'Mock',
|
||||
image: '/files/poster.png',
|
||||
onOpen: _noop,
|
||||
),
|
||||
],
|
||||
),
|
||||
_localizedApp(
|
||||
CoverCarousel(
|
||||
items: [
|
||||
for (final title in ['The Signal', 'City Lights', 'Vengeance'])
|
||||
CoverItem(
|
||||
title: title,
|
||||
subtitle: 'Mock',
|
||||
image: '/files/poster.png',
|
||||
onOpen: _noop,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -29,13 +40,15 @@ void main() {
|
||||
expect(find.text('Mock'), findsOneWidget);
|
||||
|
||||
final box = tester.getRect(find.byType(CoverCarousel));
|
||||
final gesture = await tester.startGesture(Offset(box.right - 40, box.center.dy));
|
||||
final gesture = await tester.startGesture(
|
||||
Offset(box.right - 40, box.center.dy),
|
||||
);
|
||||
await gesture.moveBy(const Offset(-500, 0));
|
||||
await tester.pump();
|
||||
|
||||
final turned = tester.widgetList<Transform>(find.byType(Transform)).any(
|
||||
(transform) => (transform.transform.entry(0, 0) - 1).abs() > 0.15,
|
||||
);
|
||||
final turned = tester
|
||||
.widgetList<Transform>(find.byType(Transform))
|
||||
.any((transform) => (transform.transform.entry(0, 0) - 1).abs() > 0.15);
|
||||
expect(turned, isTrue);
|
||||
|
||||
await gesture.moveBy(const Offset(-200, 0));
|
||||
@@ -46,6 +59,135 @@ void main() {
|
||||
expect(find.text('City Lights'), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('should_auto_advance_and_show_dots_when_multiple_items_exist', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(_localizedApp(CoverCarousel(items: items(3))));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text('Banner 0'), findsOneWidget);
|
||||
expect(find.byKey(const Key('carousel-dot-0')), findsOneWidget);
|
||||
expect(find.byKey(const Key('carousel-dot-1')), findsOneWidget);
|
||||
expect(find.byKey(const Key('carousel-dot-2')), findsOneWidget);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 4500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
|
||||
expect(find.text('Banner 1'), findsOneWidget);
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
});
|
||||
|
||||
testWidgets('should_not_schedule_auto_advance_when_only_one_item_exists', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(_localizedApp(CoverCarousel(items: items(1))));
|
||||
await tester.pump(const Duration(seconds: 12));
|
||||
|
||||
expect(find.text('Banner 0'), findsOneWidget);
|
||||
expect(find.byKey(const Key('carousel-dot-0')), findsOneWidget);
|
||||
expect(find.byKey(const Key('carousel-dot-1')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_pause_auto_advance_while_pointer_is_down', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(_localizedApp(CoverCarousel(items: items(3))));
|
||||
await tester.pump();
|
||||
final box = tester.getRect(find.byType(CoverCarousel));
|
||||
final gesture = await tester.startGesture(box.center);
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
|
||||
expect(find.text('Banner 0'), findsOneWidget);
|
||||
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 4500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
|
||||
expect(find.text('Banner 1'), findsOneWidget);
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
});
|
||||
|
||||
testWidgets('should_pause_when_inactive_and_resume_when_active_again', (
|
||||
tester,
|
||||
) async {
|
||||
final active = ValueNotifier(false);
|
||||
await tester.pumpWidget(
|
||||
_localizedApp(
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: active,
|
||||
builder: (context, enabled, _) =>
|
||||
CoverCarousel(items: items(2), active: enabled),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump(const Duration(seconds: 6));
|
||||
expect(find.text('Banner 0'), findsOneWidget);
|
||||
|
||||
active.value = true;
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 4500));
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
expect(find.text('Banner 1'), findsOneWidget);
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
});
|
||||
|
||||
testWidgets('should_show_icon_fallback_when_banner_image_is_empty', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
_localizedApp(
|
||||
CoverCarousel(
|
||||
items: const [
|
||||
CoverItem(
|
||||
title: 'Broken image',
|
||||
subtitle: '',
|
||||
image: '',
|
||||
onOpen: _noop,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byKey(const Key('carousel-image-fallback')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'should_keep_existing_open_callback_when_active_poster_is_tapped',
|
||||
(tester) async {
|
||||
var opened = 0;
|
||||
await tester.pumpWidget(
|
||||
_localizedApp(
|
||||
CoverCarousel(
|
||||
items: [
|
||||
CoverItem(
|
||||
title: 'Tap me',
|
||||
subtitle: '',
|
||||
image: '/files/banner.png',
|
||||
onOpen: () => opened++,
|
||||
),
|
||||
...items(1),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(find.text('Tap me'));
|
||||
expect(opened, 1);
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _noop() {}
|
||||
|
||||
Widget _localizedApp(Widget child) => MaterialApp(
|
||||
locale: const Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(body: child),
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
||||
import 'package:fruitshorts/core/data/remote/notice_api.dart';
|
||||
|
||||
import '../support/fake_http_adapter.dart';
|
||||
|
||||
void main() {
|
||||
test('should_parse_notice_page_with_string_ids_when_requested', () async {
|
||||
final adapter = FakeHttpAdapter(
|
||||
(_) async => FakeHttpResponse(
|
||||
jsonEncode({
|
||||
'code': 0,
|
||||
'message': 'ok',
|
||||
'data': {
|
||||
'total': '3',
|
||||
'list': [
|
||||
{
|
||||
'id': '2111012753262120962',
|
||||
'title': 'Maintenance',
|
||||
'content': 'Line one\nLine two',
|
||||
'type': 'SYSTEM',
|
||||
'publishAt': '2026-10-17T12:30:00',
|
||||
'expireAt': null,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
final api = NoticeApi(
|
||||
ApiClient(baseUrl: 'http://127.0.0.1:8081', adapter: adapter),
|
||||
);
|
||||
|
||||
final page = await api.page(pageIndex: 2, pageSize: 20);
|
||||
|
||||
expect(adapter.requests.single.uri.path, '/app/notices');
|
||||
expect(adapter.requests.single.queryParameters, {
|
||||
'pageIndex': '2',
|
||||
'pageSize': '20',
|
||||
});
|
||||
expect(page.total, 3);
|
||||
expect(page.notices.single.id, '2111012753262120962');
|
||||
expect(page.notices.single.content, 'Line one\nLine two');
|
||||
expect(page.notices.single.type, 'SYSTEM');
|
||||
expect(page.notices.single.publishAt, DateTime(2026, 10, 17, 12, 30));
|
||||
expect(page.notices.single.expireAt, isNull);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/data/models/notice.dart';
|
||||
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/notice_repository.dart';
|
||||
import 'package:fruitshorts/core/data/services/notices_controller.dart';
|
||||
|
||||
void main() {
|
||||
test('should_append_next_page_until_loaded_count_reaches_total', () async {
|
||||
final repository = ScriptedNoticeRepository([
|
||||
NoticePage(total: 3, notices: [_notice('1'), _notice('2')]),
|
||||
NoticePage(total: 3, notices: [_notice('3')]),
|
||||
]);
|
||||
final controller = NoticesController(repository);
|
||||
|
||||
await controller.load();
|
||||
expect(controller.notices.map((item) => item.id), ['1', '2']);
|
||||
expect(controller.hasMore, isTrue);
|
||||
|
||||
await controller.loadMore();
|
||||
expect(controller.notices.map((item) => item.id), ['1', '2', '3']);
|
||||
expect(controller.hasMore, isFalse);
|
||||
expect(repository.calls.map((call) => call.pageIndex), [1, 2]);
|
||||
});
|
||||
|
||||
test('should_show_empty_state_when_total_is_zero', () async {
|
||||
final controller = NoticesController(
|
||||
ScriptedNoticeRepository([const NoticePage(total: 0)]),
|
||||
);
|
||||
|
||||
await controller.load();
|
||||
|
||||
expect(controller.loading, isFalse);
|
||||
expect(controller.notices, isEmpty);
|
||||
expect(controller.hasMore, isFalse);
|
||||
expect(controller.error, isNull);
|
||||
});
|
||||
|
||||
test('should_preserve_backend_message_when_page_load_fails', () async {
|
||||
final repository = ScriptedNoticeRepository([])
|
||||
..error = const ApiException(500, '公告服务暂不可用');
|
||||
final controller = NoticesController(repository);
|
||||
|
||||
await controller.load();
|
||||
|
||||
expect(controller.error, '公告服务暂不可用');
|
||||
expect(controller.notices, isEmpty);
|
||||
});
|
||||
|
||||
test('should_replace_list_when_refreshed', () async {
|
||||
final repository = ScriptedNoticeRepository([
|
||||
NoticePage(total: 2, notices: [_notice('1'), _notice('2')]),
|
||||
NoticePage(total: 1, notices: [_notice('9')]),
|
||||
]);
|
||||
final controller = NoticesController(repository);
|
||||
await controller.load();
|
||||
|
||||
await controller.refresh();
|
||||
|
||||
expect(controller.notices.map((item) => item.id), ['9']);
|
||||
expect(controller.hasMore, isFalse);
|
||||
});
|
||||
}
|
||||
|
||||
AppNotice _notice(String id) => AppNotice(
|
||||
id: id,
|
||||
title: 'Notice $id',
|
||||
content: 'Content $id',
|
||||
type: 'SYSTEM',
|
||||
publishAt: DateTime(2026, 10, 17, 12),
|
||||
);
|
||||
|
||||
class ScriptedNoticeRepository implements NoticeRepository {
|
||||
ScriptedNoticeRepository(this.pages);
|
||||
|
||||
final List<NoticePage> pages;
|
||||
final List<NoticePageCall> calls = [];
|
||||
Object? error;
|
||||
|
||||
@override
|
||||
Future<NoticePage> page({required int pageIndex, int pageSize = 20}) async {
|
||||
calls.add(NoticePageCall(pageIndex: pageIndex, pageSize: pageSize));
|
||||
if (error != null) throw error!;
|
||||
if (pages.isEmpty) return const NoticePage(total: 0);
|
||||
return pages.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
class NoticePageCall {
|
||||
const NoticePageCall({required this.pageIndex, required this.pageSize});
|
||||
|
||||
final int pageIndex;
|
||||
final int pageSize;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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/notice.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: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/fake_notice_repository.dart';
|
||||
import '../support/widget_player.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_open_notices_from_mine_menu_without_login_gate', (
|
||||
tester,
|
||||
) async {
|
||||
SharedPreferencesAsyncPlatform.instance =
|
||||
InMemorySharedPreferencesAsync.empty();
|
||||
tester.view.physicalSize = const Size(390, 844);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final catalog = FakeCatalogRepository();
|
||||
|
||||
await tester.pumpWidget(
|
||||
LumaDramaApp(
|
||||
playerFactory: createWidgetPlayer,
|
||||
catalogRepository: catalog,
|
||||
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
||||
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
||||
favoriteRepository: InMemoryFavoriteRepository(),
|
||||
playbackRepository: FakePlaybackRepository(),
|
||||
commentRepository: FakeCommentRepository(),
|
||||
interactionRepository: FakeInteractionRepository(),
|
||||
noticeRepository: FakeNoticeRepository(
|
||||
pages: [const NoticePage(total: 0)],
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byKey(const Key('tab-my')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('mine-notices')), findsOneWidget);
|
||||
await tester.tap(find.byKey(const Key('mine-notices')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('notices-page')), findsOneWidget);
|
||||
expect(find.byKey(const Key('auth-gate-panel')), findsNothing);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/data/models/notice.dart';
|
||||
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/notice_repository.dart';
|
||||
import 'package:fruitshorts/core/formatting/localized_date_time.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/notices/notices_page.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../support/fake_notice_repository.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_render_first_page_and_append_next_page', (tester) async {
|
||||
final repository = FakeNoticeRepository(
|
||||
pages: [
|
||||
NoticePage(total: 3, notices: [_notice('1'), _notice('2')]),
|
||||
NoticePage(total: 3, notices: [_notice('3')]),
|
||||
],
|
||||
);
|
||||
await _pump(tester, repository);
|
||||
|
||||
expect(find.text('Notice 1'), findsOneWidget);
|
||||
expect(find.byKey(const Key('notices-load-more')), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const Key('notices-load-more')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Notice 3'), findsOneWidget);
|
||||
expect(repository.calls.last.pageIndex, 2);
|
||||
});
|
||||
|
||||
testWidgets('should_show_empty_state_when_no_notices_exist', (tester) async {
|
||||
await _pump(
|
||||
tester,
|
||||
FakeNoticeRepository(pages: [const NoticePage(total: 0)]),
|
||||
);
|
||||
|
||||
expect(find.byKey(const Key('notices-empty')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_show_backend_error_and_retry_when_page_fails', (
|
||||
tester,
|
||||
) async {
|
||||
await _pump(
|
||||
tester,
|
||||
FakeNoticeRepository(error: const ApiException(500, '公告加载失败')),
|
||||
);
|
||||
|
||||
expect(find.text('公告加载失败'), findsOneWidget);
|
||||
expect(find.byKey(const Key('notices-retry')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_show_loading_state_before_first_page_arrives', (
|
||||
tester,
|
||||
) async {
|
||||
final repository = FakeNoticeRepository(
|
||||
pages: [const NoticePage(total: 0)],
|
||||
delay: const Duration(milliseconds: 50),
|
||||
);
|
||||
await tester.pumpWidget(_app(repository));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.byKey(const Key('notices-loading')), findsOneWidget);
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('should_open_detail_with_newlines_and_indonesian_time', (
|
||||
tester,
|
||||
) async {
|
||||
final notice = _notice(
|
||||
'2111012753262120962',
|
||||
content: 'Line one\nLine two',
|
||||
);
|
||||
await _pump(
|
||||
tester,
|
||||
FakeNoticeRepository(
|
||||
pages: [
|
||||
NoticePage(total: 1, notices: [notice]),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(
|
||||
find.byKey(const ValueKey('notice-item-2111012753262120962')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('notice-detail-close')), findsOneWidget);
|
||||
expect(
|
||||
tester
|
||||
.widget<SelectableText>(
|
||||
find.byKey(const Key('notice-detail-content')),
|
||||
)
|
||||
.data,
|
||||
'Line one\nLine two',
|
||||
);
|
||||
final context = tester.element(
|
||||
find.byKey(const Key('notice-detail-content')),
|
||||
);
|
||||
final expected = formatLocalizedDateTime(context, notice.publishAt!);
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(AlertDialog),
|
||||
matching: find.text(expected),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('should_scroll_long_detail_without_overflow_on_small_screen', (
|
||||
tester,
|
||||
) async {
|
||||
tester.view.physicalSize = const Size(320, 568);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final notice = _notice(
|
||||
'1',
|
||||
title: 'A very long notice title that wraps across several lines',
|
||||
content: List.filled(40, 'Long notice content line.').join('\n'),
|
||||
);
|
||||
await _pump(
|
||||
tester,
|
||||
FakeNoticeRepository(
|
||||
pages: [
|
||||
NoticePage(total: 1, notices: [notice]),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('notice-item-1')));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byKey(const Key('notice-detail-content')), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
|
||||
AppNotice _notice(
|
||||
String id, {
|
||||
String? title,
|
||||
String content = 'Notice content',
|
||||
}) => AppNotice(
|
||||
id: id,
|
||||
title: title ?? 'Notice $id',
|
||||
content: content,
|
||||
type: 'SYSTEM',
|
||||
publishAt: DateTime(2026, 10, 17, 12, 30),
|
||||
);
|
||||
|
||||
Future<void> _pump(WidgetTester tester, NoticeRepository repository) async {
|
||||
await tester.pumpWidget(_app(repository));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Widget _app(NoticeRepository repository) => Provider<NoticeRepository>.value(
|
||||
value: repository,
|
||||
child: MaterialApp(
|
||||
locale: const Locale('id'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: const NoticesPage(),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,32 @@
|
||||
// Author: fruit.com.cn
|
||||
|
||||
import 'package:fruitshorts/core/data/models/notice.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/notice_repository.dart';
|
||||
|
||||
/// Scripted notice repository for controller and widget tests.
|
||||
class FakeNoticeRepository implements NoticeRepository {
|
||||
FakeNoticeRepository({List<NoticePage>? pages, this.error, this.delay})
|
||||
: pages = pages ?? [];
|
||||
|
||||
final List<NoticePage> pages;
|
||||
Object? error;
|
||||
Duration? delay;
|
||||
final List<FakeNoticeCall> calls = [];
|
||||
|
||||
@override
|
||||
Future<NoticePage> page({required int pageIndex, int pageSize = 20}) async {
|
||||
calls.add(FakeNoticeCall(pageIndex: pageIndex, pageSize: pageSize));
|
||||
if (delay != null) await Future<void>.delayed(delay!);
|
||||
if (error != null) throw error!;
|
||||
if (pages.isEmpty) return const NoticePage(total: 0);
|
||||
return pages.removeAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Captured notice page request.
|
||||
class FakeNoticeCall {
|
||||
const FakeNoticeCall({required this.pageIndex, required this.pageSize});
|
||||
|
||||
final int pageIndex;
|
||||
final int pageSize;
|
||||
}
|
||||
Reference in new issue
Block a user