feat(mobile): UI-01 album cards, photo grid and full viewer
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
/// 相册列表页签 widget 测试(P3 Task 4;UI-01 Task 3 起覆盖封面卡片样板)。
|
||||
/// 相册列表页签 widget 测试(P3 Task 4;UI-01 Task 3/4 覆盖封面卡片与
|
||||
/// 响应式列表)。
|
||||
///
|
||||
/// 覆盖:封面卡片样板(封面占位/标题回退/时间场地行/公开与上传徽章/
|
||||
/// 令牌与四开关移除)、关闭状态徽章、封面加载失败替代图、游标加载更多
|
||||
/// (第二页携带 cursor)、空态文案、首屏错误 + 重试恢复。
|
||||
/// 覆盖:封面卡片(封面占位/标题回退/时间场地行/公开与上传徽章/令牌与
|
||||
/// 四开关移除)、关闭状态徽章、封面加载失败替代图、响应式单/双列、空态
|
||||
/// CTA、游标加载更多(第二页携带 cursor)、首屏错误 + 重试恢复。
|
||||
library;
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -14,6 +15,7 @@ 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 'package:photo_live/pages/album_form_page.dart';
|
||||
import 'package:photo_live/ui/components/photo_thumbnail.dart';
|
||||
|
||||
import '../helpers/fakes.dart';
|
||||
@@ -45,6 +47,12 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
/// 固定逻辑宽度(dp=1)。
|
||||
void setSurface(WidgetTester tester, Size size) {
|
||||
tester.view.physicalSize = size;
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
}
|
||||
|
||||
testWidgets('封面卡片样板:封面占位 + 标题回退 + 时间场地 + 公开/上传徽章', (tester) async {
|
||||
final adapter = FakeAdapter(
|
||||
(o) => o.path == '/v1/albums'
|
||||
@@ -101,6 +109,67 @@ void main() {
|
||||
expect(find.byIcon(Icons.image_outlined), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('响应式列数:390 宽单列 / 900 宽双列', (tester) async {
|
||||
final adapter = FakeAdapter(
|
||||
(o) => o.path == '/v1/albums'
|
||||
? (
|
||||
200,
|
||||
cursorPageBody([
|
||||
albumJson(id: 'alb-1'),
|
||||
albumJson(id: 'alb-2'),
|
||||
]),
|
||||
)
|
||||
: throw StateError('unexpected ${o.path}'),
|
||||
);
|
||||
final client = clientOf(adapter);
|
||||
|
||||
// 手机宽度(390):单列,两卡片同宽同行不可见。
|
||||
setSurface(tester, const Size(390, 844));
|
||||
await pumpTab(tester, client);
|
||||
final card1 = tester.getRect(find.byType(PhotoThumbnail).first);
|
||||
final card2 = tester.getRect(find.byType(PhotoThumbnail).last);
|
||||
expect(card1.width, closeTo(390 - 32, 0.5), reason: '单列占满(左右 16)');
|
||||
expect(
|
||||
card1.right == card2.right && card1.left == card2.left,
|
||||
isTrue,
|
||||
reason: '两卡片同列(垂直排布)',
|
||||
);
|
||||
|
||||
// 平板宽度(900):双列并排。
|
||||
setSurface(tester, const Size(900, 1200));
|
||||
await pumpTab(tester, client);
|
||||
final wide1 = tester.getRect(find.byType(PhotoThumbnail).first);
|
||||
final wide2 = tester.getRect(find.byType(PhotoThumbnail).last);
|
||||
expect(wide1.width, closeTo((900 - 32 - 12) / 2, 0.5), reason: '双列均分');
|
||||
expect(
|
||||
wide2.left > wide1.right,
|
||||
isTrue,
|
||||
reason: '第二张在第一张右侧(双列)',
|
||||
);
|
||||
|
||||
tester.view.resetPhysicalSize();
|
||||
tester.view.resetDevicePixelRatio();
|
||||
});
|
||||
|
||||
testWidgets('空列表 → 空态文案 + 「新建相册」CTA 进入表单', (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,
|
||||
);
|
||||
// 空态 CTA(次强调按钮)→ 创建表单。
|
||||
expect(find.byType(AlbumFormPage), findsNothing);
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Create album'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(AlbumFormPage), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('滚动接近底部 → 游标加载第二页(携带 cursor)', (tester) async {
|
||||
final page1 = List<Map<String, dynamic>>.generate(
|
||||
20,
|
||||
@@ -146,20 +215,6 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user