/// 相册列表页签 widget 测试(P3 计划 Task 4)。 /// /// 覆盖:首屏加载与卡片标签行(状态/开关/时区时间/令牌短显)、游标 /// 加载更多(第二页携带 cursor)、空态文案、首屏错误 + 重试恢复。 library; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:photo_live/core/api_client.dart'; import 'package:photo_live/core/error_codes.dart'; import 'package:photo_live/features/albums/providers.dart'; import 'package:photo_live/l10n/app_localizations.dart'; import 'package:photo_live/pages/albums_tab.dart'; import '../helpers/fakes.dart'; void main() { late FakeSessionStore store; setUp(() { store = FakeSessionStore(); }); ApiClient clientOf(FakeAdapter adapter) => ApiClient( sessionStore: store, dio: Dio(BaseOptions(baseUrl: 'http://test.local')) ..httpClientAdapter = adapter, ); Future pumpTab(WidgetTester tester, ApiClient client) async { await tester.pumpWidget( ProviderScope( overrides: [apiClientProvider.overrideWith((ref) => client)], child: MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: const AlbumsTab(), ), ), ); await tester.pumpAndSettle(); } testWidgets('首屏加载 → 卡片渲染标题/状态/开关标签/时区时间/令牌短显', (tester) async { final adapter = FakeAdapter( (o) => o.path == '/v1/albums' ? (200, cursorPageBody([albumJson( status: 'offline', uploadEnabled: true, publicEnabled: false, downloadEnabled: true, originalDownloadEnabled: false, )])) : throw StateError('unexpected ${o.path}'), ); await pumpTab(tester, clientOf(adapter)); expect(find.text('开幕晚宴'), findsOneWidget); expect(find.text('Offline'), findsOneWidget); // 四个开关标签(色调区分开关,文案一致)。 expect(find.text('Upload'), findsOneWidget); expect(find.text('Public'), findsOneWidget); expect(find.text('Download'), findsOneWidget); expect(find.text('Original'), findsOneWidget); // Asia/Shanghai 墙钟 14:30;结束未定。 expect(find.text('2026/9/18 14:30 ~ Not set'), findsOneWidget); // 令牌短显(前 10 字符 + 省略号)。 expect(find.text('sharetoken…'), findsOneWidget); expect(find.byIcon(Icons.add), findsOneWidget, reason: 'FAB 新建入口'); }); testWidgets('滚动接近底部 → 游标加载第二页(携带 cursor)', (tester) async { final page1 = List>.generate( 20, (i) => albumJson(id: 'alb-${i + 1}'), ); final page2 = List>.generate( 10, (i) => albumJson(id: 'alb-${i + 21}'), ); final adapter = FakeAdapter( (o) { if (o.path != '/v1/albums') throw StateError('unexpected ${o.path}'); final cursor = o.uri.queryParameters['cursor']; if (cursor == null) return (200, cursorPageBody(page1, nextCursor: 'c1')); if (cursor == 'c1') return (200, cursorPageBody(page2)); throw StateError('unexpected cursor $cursor'); }, ); await pumpTab(tester, clientOf(adapter)); // 首屏只拉第一页(20 条入 state;ListView 懒构建,不逐条断言渲染)。 expect(adapter.requests, hasLength(1)); final container = ProviderScope.containerOf( tester.element(find.byType(AlbumsTab)), ); expect(container.read(albumListProvider).items, hasLength(20)); // 上滑触发加载更多。 await tester.drag(find.byType(ListView), const Offset(0, -2400)); await tester.pumpAndSettle(); expect(adapter.requests, hasLength(2)); expect( adapter.requests.last.uri.queryParameters['cursor'], 'c1', reason: '第二页携带游标 c1', ); expect( container.read(albumListProvider).items, hasLength(30), reason: '两页累积', ); }); testWidgets('空列表 → 空态文案', (tester) async { final adapter = FakeAdapter( (o) => o.path == '/v1/albums' ? (200, cursorPageBody(>[])) : throw StateError('unexpected ${o.path}'), ); await pumpTab(tester, clientOf(adapter)); expect( find.text('No albums yet. Tap "Create album" to get started'), findsOneWidget, ); }); testWidgets('首屏错误 → 错误视图,重试后恢复', (tester) async { var failFirst = true; final adapter = FakeAdapter( (o) { if (o.path != '/v1/albums') throw StateError('unexpected ${o.path}'); if (failFirst) { failFirst = false; return (500, errorBody(ErrorCodes.internalError)); } return (200, cursorPageBody([albumJson()])); }, ); await pumpTab(tester, clientOf(adapter)); expect( find.text('Service temporarily unavailable. Please retry later'), findsOneWidget, ); await tester.tap(find.text('Retry')); await tester.pumpAndSettle(); expect(find.text('开幕晚宴'), findsOneWidget); }); }