2026-09-22 02:45:34 +08:00
|
|
|
|
/// 相册分享页 widget 测试(UI-03 §4 页面 19)。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 覆盖:全屏页结构(标题/真实封面占位/名称/日期地点/二维码/扫码说明/
|
|
|
|
|
|
/// 可选中链接)、链接携带正确相册 token 与 `?lang=` 语言参数、Copy link
|
|
|
|
|
|
/// 主操作写剪贴板 + 已复制提示、Open in browser 次操作经注入替身启动器
|
|
|
|
|
|
/// 打开并退出页面、未公开相册显示「该链接目前无法公开浏览」琥珀提示
|
2026-09-29 11:04:13 +08:00
|
|
|
|
/// (公开相册不显示)、海报弹层内「中文/English」切换(默认跟 App 语言,
|
2026-09-29 11:25:49 +08:00
|
|
|
|
/// 提示文案/相册标题/二维码 lang 随切换,弹层 chrome 仍跟 App 语言)、
|
|
|
|
|
|
/// 下载海报反馈在弹层内可见(成功=按钮翻成功态自动复原;失败=弹层内
|
|
|
|
|
|
/// 红字可重试;SnackBar 在模态弹层下不可见,不得作为反馈通道)。
|
2026-09-22 02:45:34 +08:00
|
|
|
|
library;
|
|
|
|
|
|
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
|
|
import 'package:photo_live/features/albums/models.dart';
|
|
|
|
|
|
import 'package:photo_live/l10n/app_localizations.dart';
|
|
|
|
|
|
import 'package:photo_live/pages/share_qr_dialog.dart';
|
|
|
|
|
|
import 'package:qr_flutter/qr_flutter.dart';
|
|
|
|
|
|
|
|
|
|
|
|
import '../helpers/fakes.dart';
|
|
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
|
const token = 'sharetoken000000000001';
|
|
|
|
|
|
|
|
|
|
|
|
Future<void> pumpPage(
|
|
|
|
|
|
WidgetTester tester, {
|
|
|
|
|
|
bool publicEnabled = true,
|
|
|
|
|
|
UrlLauncher? launcher,
|
2026-09-29 11:25:49 +08:00
|
|
|
|
PosterSaver? saver,
|
2026-09-22 02:45:34 +08:00
|
|
|
|
}) async {
|
|
|
|
|
|
await tester.pumpWidget(
|
|
|
|
|
|
ProviderScope(
|
|
|
|
|
|
overrides: [
|
|
|
|
|
|
if (launcher != null)
|
|
|
|
|
|
urlLauncherProvider.overrideWith((ref) => launcher),
|
2026-09-29 11:25:49 +08:00
|
|
|
|
if (saver != null)
|
|
|
|
|
|
posterSaverProvider.overrideWith((ref) => saver),
|
2026-09-22 02:45:34 +08:00
|
|
|
|
],
|
|
|
|
|
|
child: MaterialApp(
|
|
|
|
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
|
|
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
|
|
|
|
home: ShareQrPage(
|
|
|
|
|
|
album: Album.fromJson(
|
|
|
|
|
|
albumJson(publicToken: token, publicEnabled: publicEnabled),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('全屏页结构:标题/封面/名称/日期地点/二维码/扫码说明/链接', (
|
|
|
|
|
|
tester,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await pumpPage(tester);
|
|
|
|
|
|
|
|
|
|
|
|
// 全屏页 AppBar 标题(不再是 Dialog)。
|
|
|
|
|
|
expect(find.byType(AppBar), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Share album'), findsOneWidget);
|
|
|
|
|
|
// 真实封面位(无封面 → 同尺寸中性占位,不破图不空行)。
|
|
|
|
|
|
expect(find.byIcon(Icons.image_outlined), findsOneWidget);
|
|
|
|
|
|
// 名称与日期地点(en 回退:Opening Gala;上海墙钟 14:30)。
|
|
|
|
|
|
expect(find.text('Opening Gala'), findsOneWidget);
|
|
|
|
|
|
expect(
|
2026-09-24 16:20:47 +08:00
|
|
|
|
find.text('2026/9/18 · 大宴会厅'),
|
2026-09-22 02:45:34 +08:00
|
|
|
|
findsOneWidget,
|
|
|
|
|
|
reason: '开始时间(相册时区;结束未定不伪造区间)+ 场地(展示回退链)',
|
|
|
|
|
|
);
|
2026-09-23 11:27:32 +08:00
|
|
|
|
// UI06 三方式:系统分享(默认)/ 生成二维码海报 / 复制链接。
|
2026-09-23 16:05:53 +08:00
|
|
|
|
expect(find.text('Share to...'), findsOneWidget);
|
2026-09-23 11:27:32 +08:00
|
|
|
|
expect(find.text('Generate QR poster'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Copy link'), findsOneWidget);
|
|
|
|
|
|
// 可选中链接(二维码与复制共用同一 URL)。
|
2026-09-22 02:45:34 +08:00
|
|
|
|
final url = 'http://10.0.2.2:5175/a/$token?lang=en';
|
|
|
|
|
|
expect(
|
|
|
|
|
|
tester.widget<SelectableText>(find.byType(SelectableText)).data,
|
|
|
|
|
|
url,
|
2026-09-23 11:27:32 +08:00
|
|
|
|
reason: '链接文本(?lang= 随 App 语言)',
|
2026-09-22 02:45:34 +08:00
|
|
|
|
);
|
2026-09-23 11:27:32 +08:00
|
|
|
|
|
|
|
|
|
|
// 点「生成二维码海报」→ 弹层:标题/时间/地址/二维码/下载按钮。
|
|
|
|
|
|
await tester.tap(find.text('Generate QR poster'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
expect(find.byType(QrImageView), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Scan to view photos'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Download poster image'), findsOneWidget);
|
2026-09-23 16:26:32 +08:00
|
|
|
|
// UI06:海报日期粒度(无时间)。
|
|
|
|
|
|
expect(find.text('2026/9/18'), findsOneWidget);
|
2026-09-22 02:45:34 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-29 11:04:13 +08:00
|
|
|
|
testWidgets('海报语言切换:默认跟 App 语言,切中文后提示/标题/二维码随之', (
|
|
|
|
|
|
tester,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await pumpPage(tester);
|
|
|
|
|
|
await tester.tap(find.text('Generate QR poster'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
// 默认 English 档(App 语言 en):英文提示 + lang=en 的二维码
|
|
|
|
|
|
// (qr_flutter 4.1 无公开 data getter,经 semanticsLabel 断言编码内容)。
|
|
|
|
|
|
expect(find.text('Scan to view photos'), findsOneWidget);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
tester.widget<QrImageView>(find.byType(QrImageView)).semanticsLabel,
|
|
|
|
|
|
'http://10.0.2.2:5175/a/$token?lang=en',
|
|
|
|
|
|
reason: '二维码 lang 跟海报语言(默认=App 语言前缀)',
|
|
|
|
|
|
);
|
|
|
|
|
|
// 两档切换(语言名以自身语言书写,与语言选择面板同约定)。
|
|
|
|
|
|
expect(find.text('中文'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('English'), findsOneWidget);
|
|
|
|
|
|
// 英文档标题取英文翻译(中文翻译不出现在海报上)。
|
|
|
|
|
|
expect(find.text('开幕晚宴'), findsNothing);
|
|
|
|
|
|
|
|
|
|
|
|
// 切「中文」:提示/标题换中文、二维码 lang=zh-CN。
|
|
|
|
|
|
await tester.tap(find.text('中文'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
expect(find.text('扫码查看照片'), findsOneWidget);
|
|
|
|
|
|
expect(find.text('Scan to view photos'), findsNothing);
|
|
|
|
|
|
expect(find.text('开幕晚宴'), findsOneWidget);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
tester.widget<QrImageView>(find.byType(QrImageView)).semanticsLabel,
|
|
|
|
|
|
'http://10.0.2.2:5175/a/$token?lang=zh-CN',
|
|
|
|
|
|
);
|
|
|
|
|
|
// 弹层 chrome(标题/下载按钮)不随海报语言切换,仍跟 App 语言。
|
|
|
|
|
|
expect(find.text('Download poster image'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-29 11:25:49 +08:00
|
|
|
|
testWidgets('下载海报成功:按钮翻成功态并自动复原,全程无 SnackBar', (
|
|
|
|
|
|
tester,
|
|
|
|
|
|
) async {
|
|
|
|
|
|
await pumpPage(tester, saver: (_) async {});
|
|
|
|
|
|
await tester.tap(find.text('Generate QR poster'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.ensureVisible(find.text('Download poster image'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.text('Download poster image'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
// 反馈必须在弹层内可见(SnackBar 被模态弹层遮挡,禁用)。
|
|
|
|
|
|
expect(find.byType(SnackBar), findsNothing);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.descendant(
|
|
|
|
|
|
of: find.byType(BottomSheet),
|
|
|
|
|
|
matching: find.text('Poster saved to gallery'),
|
|
|
|
|
|
),
|
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
|
reason: '成功反馈显示在下载按钮上(弹层内)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 约 2.5s 后自动复原,可再次下载(配合语言切换存第二张)。
|
|
|
|
|
|
await tester.pump(const Duration(seconds: 3));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
expect(find.text('Poster saved to gallery'), findsNothing);
|
|
|
|
|
|
expect(find.text('Download poster image'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('下载海报失败:弹层内红字提示且可重试', (tester) async {
|
|
|
|
|
|
var calls = 0;
|
|
|
|
|
|
await pumpPage(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
saver: (_) async {
|
|
|
|
|
|
calls++;
|
|
|
|
|
|
throw StateError('save failed');
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
await tester.tap(find.text('Generate QR poster'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.ensureVisible(find.text('Download poster image'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
await tester.tap(find.text('Download poster image'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(find.byType(SnackBar), findsNothing);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.descendant(
|
|
|
|
|
|
of: find.byType(BottomSheet),
|
|
|
|
|
|
matching: find.text('Failed to save poster'),
|
|
|
|
|
|
),
|
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
|
reason: '失败反馈在弹层内(对齐隐藏照片弹层的面板内提示模式)',
|
|
|
|
|
|
);
|
|
|
|
|
|
// 按钮复位可重试。
|
|
|
|
|
|
expect(find.text('Download poster image'), findsOneWidget);
|
|
|
|
|
|
|
|
|
|
|
|
await tester.ensureVisible(find.text('Download poster image'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.tap(find.text('Download poster image'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
expect(calls, 2, reason: '失败后可重试');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-09-22 02:45:34 +08:00
|
|
|
|
testWidgets('Copy link(主操作)→ 剪贴板收到带 lang 的 URL + 已复制提示',
|
|
|
|
|
|
(tester) async {
|
|
|
|
|
|
final clipboardCalls = <MethodCall>[];
|
|
|
|
|
|
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
|
|
|
|
SystemChannels.platform,
|
|
|
|
|
|
(call) async {
|
|
|
|
|
|
clipboardCalls.add(call);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await pumpPage(tester);
|
|
|
|
|
|
await tester.ensureVisible(find.text('Copy link'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.tap(find.text('Copy link'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
|
clipboardCalls.any(
|
|
|
|
|
|
(c) =>
|
|
|
|
|
|
c.method == 'Clipboard.setData' &&
|
|
|
|
|
|
(c.arguments as Map<String, dynamic>)['text'] ==
|
|
|
|
|
|
'http://10.0.2.2:5175/a/$token?lang=en',
|
|
|
|
|
|
),
|
|
|
|
|
|
isTrue,
|
|
|
|
|
|
);
|
|
|
|
|
|
expect(find.text('Copied'), findsOneWidget);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('Open in browser(次操作)→ 启动器收到 URL + 退出分享页',
|
|
|
|
|
|
(tester) async {
|
|
|
|
|
|
final launchedUrls = <String>[];
|
|
|
|
|
|
await pumpPage(
|
|
|
|
|
|
tester,
|
|
|
|
|
|
launcher: (url) async {
|
|
|
|
|
|
launchedUrls.add(url);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await tester.ensureVisible(find.text('Open in browser'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
await tester.tap(find.text('Open in browser'));
|
|
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
|
|
|
|
|
|
|
|
expect(launchedUrls, ['http://10.0.2.2:5175/a/$token?lang=en']);
|
|
|
|
|
|
expect(find.byType(ShareQrPage), findsNothing, reason: '打开成功后退出');
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
testWidgets('未公开相册:琥珀提示链接不可公开浏览;公开相册不提示',
|
|
|
|
|
|
(tester) async {
|
|
|
|
|
|
await pumpPage(tester, publicEnabled: false);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.text('This link is not publicly accessible yet'),
|
|
|
|
|
|
findsOneWidget,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await pumpPage(tester, publicEnabled: true);
|
|
|
|
|
|
expect(
|
|
|
|
|
|
find.text('This link is not publicly accessible yet'),
|
|
|
|
|
|
findsNothing,
|
|
|
|
|
|
reason: '公开相册不得显示不可浏览提示',
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|