170 lines
4.9 KiB
Dart
170 lines
4.9 KiB
Dart
// 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/core/widgets/empty_state.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);
|
|
expect(find.byType(EmptyState), 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(),
|
|
),
|
|
);
|