bbea1dd364
Co-Authored-By: Claude Code <noreply@anthropic.com>
158 lines
5.3 KiB
Dart
158 lines
5.3 KiB
Dart
/// 相册列表页签 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<void> 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<Map<String, dynamic>>.generate(
|
|
20,
|
|
(i) => albumJson(id: 'alb-${i + 1}'),
|
|
);
|
|
final page2 = List<Map<String, dynamic>>.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(<Map<String, dynamic>>[]))
|
|
: 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);
|
|
});
|
|
}
|