// 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/app_glow_panel.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); await tester.drag( find.byKey(const Key('notices-list')), const Offset(0, -300), ); 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( 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(AppGlowDialog), 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 _pump(WidgetTester tester, NoticeRepository repository) async { await tester.pumpWidget(_app(repository)); await tester.pumpAndSettle(); } Widget _app(NoticeRepository repository) => Provider.value( value: repository, child: MaterialApp( locale: const Locale('id'), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const NoticesPage(), ), );