feat(mobile): UI-03 album workspace pages (05-08)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
/// 相册详情页 widget 测试(UI-01 §4.2 重构后)。
|
||||
/// 相册详情页 widget 测试(UI-03 §4 页面 06-08 重设计后)。
|
||||
///
|
||||
/// 覆盖:紧凑头部(封面占位 + 标题 + 时区时间场地行)→ 状态徽章 →
|
||||
/// 照片入口(最近上传预览条 + 管理照片 / 空态「拍摄第一张照片」)→ 次级
|
||||
/// 管理入口(ListTile)→ 底部固定上传栏(拍摄上传主 / 从相册选择次,上
|
||||
/// 传关闭时禁用并说明);协作者(scope all 403 → own)隐藏管理入口;分类
|
||||
/// 管理三流(新增载荷、空名校验、编辑 PATCH、删除确认);底部栏直达连拍
|
||||
/// 页与多选入队。
|
||||
/// 覆盖:封面 → 标题/时区时间场地 → 状态徽章 → 次级四入口(Photos/
|
||||
/// Members/Categories/Share)→ Recent photos 三列网格 + See all → 底部
|
||||
/// 固定上传栏(拍摄上传主 / 从相册选择次,上传关闭时禁用并说明);空相册
|
||||
/// 安静占位;协作者(scope all 403 → own)隐藏管理入口;分类管理三流
|
||||
/// (新增载荷、空名校验、编辑 PATCH、删除确认);底部栏直达连拍页与多选
|
||||
/// 入队。
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
@@ -105,7 +105,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets('布局结构:头部 → 状态 → 照片入口 → 次级操作 → 底部上传栏',
|
||||
testWidgets('布局结构:封面 → 信息 → 四入口 → Recent photos → 底部上传栏',
|
||||
(tester) async {
|
||||
final adapter = detailAdapter(
|
||||
photos: List<Map<String, dynamic>>.generate(
|
||||
@@ -118,8 +118,8 @@ void main() {
|
||||
);
|
||||
await pumpDetail(tester, clientOf(adapter));
|
||||
|
||||
// 紧凑头部:封面占位(无 cover → 中性占位,非转圈)+ 标题 + 单行
|
||||
// 时间/场地(相册时区墙钟 + 场地语言回退)。
|
||||
// 封面占位(无 cover → 中性占位,非转圈)+ 标题 + 单行时间/场地
|
||||
// (相册时区墙钟 + 场地语言回退)。
|
||||
final body = find.byType(SingleChildScrollView);
|
||||
expect(
|
||||
find.descendant(of: body, matching: find.text('Opening Gala')),
|
||||
@@ -130,7 +130,7 @@ void main() {
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byIcon(Icons.image_outlined), findsOneWidget);
|
||||
// 预览条缩略图(签名 URL 测试环境失败 → 替代图,不破图)。
|
||||
// Recent photos 网格缩略图(签名 URL 测试环境失败 → 替代图,不破图)。
|
||||
expect(find.byIcon(Icons.broken_image_outlined), findsNWidgets(3));
|
||||
|
||||
// 状态区:公开/上传徽章(关闭不等于不公开,分别呈现)。
|
||||
@@ -140,14 +140,23 @@ void main() {
|
||||
expect(find.text('Original'), findsNothing);
|
||||
expect(find.text('sharetoken000000000001'), findsNothing);
|
||||
|
||||
// 照片入口:分区标题 + 「管理照片」入口 + 3 张预览缩略图。
|
||||
expect(find.text('Manage photos'), findsOneWidget);
|
||||
// Recent photos:分区标题 + 「See all」入口 + 三列网格。
|
||||
expect(find.text('Recent photos'), findsOneWidget);
|
||||
expect(find.text('See all'), findsOneWidget);
|
||||
|
||||
// 次级操作(ListTile,不再与主操作同级)。
|
||||
expect(find.widgetWithText(ListTile, 'Edit'), findsOneWidget);
|
||||
expect(find.widgetWithText(ListTile, 'Members'), findsOneWidget);
|
||||
expect(find.widgetWithText(ListTile, 'Categories'), findsOneWidget);
|
||||
expect(find.widgetWithText(ListTile, 'Share QR code'), findsOneWidget);
|
||||
// 次级四入口(管理者全量)。入口文字限定在四入口卡内(页面下方
|
||||
// 「分类管理」分区标题同为 Categories 文案,按 Card 作用域区分)。
|
||||
final tilesCard = find.byType(Card).first;
|
||||
expect(find.descendant(of: tilesCard, matching: find.text('Photos')),
|
||||
findsOneWidget);
|
||||
expect(find.descendant(of: tilesCard, matching: find.text('Members')),
|
||||
findsOneWidget);
|
||||
expect(find.descendant(of: tilesCard, matching: find.text('Categories')),
|
||||
findsOneWidget);
|
||||
expect(find.descendant(of: tilesCard, matching: find.text('Share')),
|
||||
findsOneWidget);
|
||||
// 顶栏「⋯」操作菜单入口。
|
||||
expect(find.byIcon(Icons.more_vert), findsOneWidget);
|
||||
|
||||
// 底部固定上传栏:拍摄上传(主)+ 从相册选择(次)。
|
||||
final capture = find.widgetWithText(FilledButton, 'Capture and upload');
|
||||
@@ -155,34 +164,44 @@ void main() {
|
||||
expect(capture, findsOneWidget);
|
||||
expect(pick, findsOneWidget);
|
||||
|
||||
// 垂直顺序:标题 → 徽章 → 管理照片 → 底部栏(次级操作在 body 列内,
|
||||
// 其在树中先于 bottomNavigationBar,几何位置随滚动变化不作断言)。
|
||||
// 垂直顺序:标题 → 徽章 → 四入口 → Recent photos → 底部栏。全宽
|
||||
// 16:9 封面较高,先滚动让 Recent photos 入屏再比较几何位置。
|
||||
await reveal(tester, find.text('Recent photos'));
|
||||
final titleTop = tester
|
||||
.getRect(find.descendant(of: body, matching: find.text('Opening Gala')))
|
||||
.top;
|
||||
final badgeTop = tester.getRect(find.text('Public')).top;
|
||||
final manageTop = tester.getRect(find.text('Manage photos')).top;
|
||||
final tilesTop = tester
|
||||
.getRect(find.descendant(of: tilesCard, matching: find.text('Photos')))
|
||||
.top;
|
||||
final recentTop = tester.getRect(find.text('Recent photos')).top;
|
||||
final captureTop = tester.getRect(capture).top;
|
||||
expect(titleTop, lessThan(badgeTop));
|
||||
expect(badgeTop, lessThan(manageTop));
|
||||
expect(manageTop, lessThan(captureTop));
|
||||
expect(badgeTop, lessThan(tilesTop));
|
||||
expect(tilesTop, lessThan(recentTop));
|
||||
expect(recentTop, lessThan(captureTop));
|
||||
});
|
||||
|
||||
testWidgets('无照片:空态引导「拍摄第一张照片」(上传开启时可用)',
|
||||
testWidgets('无照片:空相册安静占位「Ready for your first photo」',
|
||||
(tester) async {
|
||||
await pumpDetail(tester, clientOf(detailAdapter()));
|
||||
|
||||
expect(find.text('No photos yet'), findsOneWidget);
|
||||
final cta = find.widgetWithText(FilledButton, 'Take the first photo');
|
||||
expect(cta, findsOneWidget);
|
||||
expect(find.text('Ready for your first photo'), findsOneWidget);
|
||||
expect(
|
||||
tester.widget<FilledButton>(cta).onPressed,
|
||||
find.text('Photos you take or pick will appear here'),
|
||||
findsOneWidget,
|
||||
);
|
||||
// 空态无伪造计数/示例照片,上传入口收敛到底部栏(上传开启时可用)。
|
||||
expect(find.text('No photos yet'), findsNothing);
|
||||
final capture = find.widgetWithText(FilledButton, 'Capture and upload');
|
||||
expect(
|
||||
tester.widget<FilledButton>(capture).onPressed,
|
||||
isNotNull,
|
||||
reason: '上传开启时引导可用',
|
||||
reason: '上传开启时底部栏可用',
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('上传关闭:底部栏说明原因并禁用双入口 + 引导禁用', (tester) async {
|
||||
testWidgets('上传关闭:底部栏说明原因并禁用双入口', (tester) async {
|
||||
final adapter = FakeAdapter((o) {
|
||||
if (o.path == '/v1/albums/alb-1' && o.method == 'GET') {
|
||||
return (200, albumJson(categories: categories, uploadEnabled: false));
|
||||
@@ -216,15 +235,6 @@ void main() {
|
||||
.onPressed,
|
||||
isNull,
|
||||
);
|
||||
expect(
|
||||
tester
|
||||
.widget<FilledButton>(
|
||||
find.widgetWithText(FilledButton, 'Take the first photo'),
|
||||
)
|
||||
.onPressed,
|
||||
isNull,
|
||||
reason: '空态引导与底部栏同一开关',
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('协作者(all 403 → own):隐藏管理入口,仅见自己上传预览',
|
||||
@@ -249,14 +259,14 @@ void main() {
|
||||
expect(photoRequests.first.uri.queryParameters['scope'], 'all');
|
||||
expect(photoRequests.last.uri.queryParameters['scope'], 'own');
|
||||
|
||||
// 管理入口仅创建者/管理员可见;分享对全体成员开放。
|
||||
expect(find.widgetWithText(ListTile, 'Edit'), findsNothing);
|
||||
expect(find.widgetWithText(ListTile, 'Members'), findsNothing);
|
||||
// 管理入口仅创建者/管理员可见;Photos/Share 对全体成员开放。
|
||||
expect(find.text('Members'), findsNothing);
|
||||
expect(find.text('Categories'), findsNothing, reason: '含分区一并隐藏');
|
||||
expect(find.widgetWithText(ListTile, 'Share QR code'), findsOneWidget);
|
||||
expect(find.text('Photos'), findsOneWidget);
|
||||
expect(find.text('Share'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.add_circle_outline), findsNothing);
|
||||
|
||||
// 预览条退化为自己的上传(1 张;头部封面占位另 1 张)。
|
||||
// 预览网格退化为自己的上传(1 张;封面占位另 1 张)。
|
||||
expect(find.byIcon(Icons.broken_image_outlined), findsOneWidget);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user