Files
platforms/apps/user_app/test/ui/login_page_test.dart
czl231 3ef33b531d 已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
2026-09-13 00:57:32 +08:00

196 lines
8.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 功能描述:验证用户端登录页字段、必填校验、提交行为和接口错误中文化。
// 版本:1.2.0
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:user_app/app/dependencies.dart';
import 'package:user_app/data/services/api_client.dart';
import 'package:user_app/data/services/secure_session_store.dart';
import 'package:user_app/ui/features/auth/login_page.dart';
/// 记录登录调用,验证本地校验是否正确阻止请求。
class _RecordingUserSession extends UserSession {
_RecordingUserSession() : super(SecureSessionStore());
int loginCallCount = 0;
String? lastPhone;
String? lastPassword;
String? lastCode;
int codeCalls = 0;
@override
Future<String> sendCode(String phone, String purpose) async {
codeCalls++;
return 'request-1';
}
@override
Future<void> login({
required String phone,
required String password,
String? verificationCode,
String? requestIdentity,
}) async {
loginCallCount += 1;
lastPhone = phone;
lastPassword = password;
lastCode = verificationCode;
}
}
/// 模拟服务端确认短信未实际发送,验证页面不会显示虚假倒计时。
class _UnavailableCodeSession extends _RecordingUserSession {
@override
Future<String> sendCode(String phone, String purpose) async {
throw const ApiException(2410, '短信验证码暂未开放');
}
}
/// 验证用户端登录界面的关键交互。
void main() {
testWidgets('显隐保留密码和焦点,重复选择当前登录方式不清空输入', (tester) async {
await tester.pumpWidget(MaterialApp(home: LoginPage(session: _RecordingUserSession())));
await tester.tap(find.text('密码登录'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField).at(1), 'Input-test-123');
await tester.tap(find.byTooltip('显示密码'));
await tester.pumpAndSettle();
var field = tester.widget<TextField>(find.byType(TextField).at(1));
expect(field.controller!.text, 'Input-test-123');
expect(field.obscureText, isFalse);
expect(field.focusNode!.hasFocus, isTrue);
await tester.tap(find.text('密码登录'));
await tester.pumpAndSettle();
expect(
tester.widget<TextField>(find.byType(TextField).at(1)).controller!.text,
'Input-test-123',
);
await tester.tap(find.byTooltip('隐藏密码'));
await tester.pumpAndSettle();
field = tester.widget<TextField>(find.byType(TextField).at(1));
expect(field.obscureText, isTrue);
expect(field.controller!.text, 'Input-test-123');
expect(tester.takeException(), isNull);
});
testWidgets('有焦点时切换验证码建立新连接并清除上一种凭据', (tester) async {
await tester.pumpWidget(MaterialApp(home: LoginPage(session: _RecordingUserSession())));
await tester.tap(find.text('密码登录'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField).at(1), 'Input-test-123');
await tester.tap(find.text('验证码登录'));
await tester.pumpAndSettle();
final field = tester.widget<TextField>(find.byType(TextField).at(1));
expect(field.controller!.text, isEmpty);
expect(field.keyboardType, TextInputType.number);
expect(field.obscureText, isFalse);
expect(field.focusNode!.hasFocus, isTrue);
await tester.enterText(find.byType(TextField).at(1), '314159');
expect(field.controller!.text, '314159');
expect(tester.takeException(), isNull);
});
testWidgets('login page exposes phone and password fields', (tester) async {
final session = UserSession(SecureSessionStore());
await tester.pumpWidget(MaterialApp(home: LoginPage(session: session)));
expect(find.bySemanticsLabel('瓶安芯'), findsOneWidget);
expect(find.byType(TextField), findsNWidgets(2));
expect(find.text('登录'), findsOneWidget);
});
testWidgets('手机号错误且密码为空时同时提示并阻止登录调用', (tester) async {
final session = _RecordingUserSession();
await tester.pumpWidget(MaterialApp(home: LoginPage(session: session)));
await tester.tap(find.text('密码登录'));
await tester.pump();
await tester.enterText(find.byType(TextField).first, '+8613800138000');
await tester.ensureVisible(find.text('登录'));
await tester.tap(find.text('登录'));
await tester.pump();
expect(find.text('请输入正确的11位手机号'), findsOneWidget);
expect(tester.widget<TextField>(find.byType(TextField).at(1)).decoration?.errorText, '请输入密码');
expect(find.text('Invalid Argument'), findsNothing);
expect(session.loginCallCount, 0);
expect(tester.widget<TextField>(find.byType(TextField).first).focusNode?.hasFocus, isTrue);
});
testWidgets('合法手机号配空密码时提示密码必填并阻止登录调用', (tester) async {
final session = _RecordingUserSession();
await tester.pumpWidget(MaterialApp(home: LoginPage(session: session)));
await tester.tap(find.text('密码登录'));
await tester.pump();
await tester.enterText(find.byType(TextField).first, '13800138000');
await tester.ensureVisible(find.text('登录'));
await tester.tap(find.text('登录'));
await tester.pump();
expect(find.text('请输入正确的11位手机号'), findsNothing);
expect(tester.widget<TextField>(find.byType(TextField).at(1)).decoration?.errorText, '请输入密码');
expect(session.loginCallCount, 0);
expect(tester.widget<TextField>(find.byType(TextField).at(1)).focusNode?.hasFocus, isTrue);
});
testWidgets('修改字段清除对应错误并由回车原样提交密码', (tester) async {
final session = _RecordingUserSession();
await tester.pumpWidget(MaterialApp(home: LoginPage(session: session)));
await tester.tap(find.text('密码登录'));
await tester.pump();
await tester.ensureVisible(find.text('登录'));
await tester.tap(find.text('登录'));
await tester.pump();
await tester.enterText(find.byType(TextField).first, '13800138000');
await tester.pump();
expect(find.text('请输入正确的11位手机号'), findsNothing);
expect(tester.widget<TextField>(find.byType(TextField).at(1)).decoration?.errorText, '请输入密码');
await tester.enterText(find.byType(TextField).at(1), ' 密码123 ');
await tester.pump();
expect(tester.widget<TextField>(find.byType(TextField).at(1)).decoration?.errorText, isNull);
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pump();
expect(session.loginCallCount, 1);
expect(session.lastPhone, '13800138000');
expect(session.lastPassword, ' 密码123 ');
});
test('接口错误优先按错误码中文化并屏蔽未知英文', () {
expect(localizeApiErrorMessage(1704, 'Invalid Argument'), '请求参数错误');
expect(localizeApiErrorMessage(1108, 'Password Incorrect'), '密码错误');
expect(localizeApiErrorMessage(2410, 'service unavailable'), '短信验证码暂未开放');
expect(localizeApiErrorMessage(9999, '业务处理失败'), '业务处理失败');
expect(localizeApiErrorMessage(9999, 'Unexpected Error'), '操作失败,请稍后重试');
});
testWidgets('验证码发送后修改手机号不得复用旧请求', (tester) async {
final session = _RecordingUserSession();
await tester.pumpWidget(MaterialApp(home: LoginPage(session: session)));
await tester.enterText(find.byType(TextField).first, '13800138000');
await tester.tap(find.text('获取验证码'));
await tester.pump();
await tester.enterText(find.byType(TextField).first, '13800138001');
await tester.enterText(find.byType(TextField).at(1), '123456');
await tester.ensureVisible(find.text('登录'));
await tester.tap(find.text('登录'));
await tester.pump();
expect(find.text('请先获取当前手机号的验证码'), findsOneWidget);
expect(session.loginCallCount, 0);
await tester.pumpWidget(const SizedBox());
});
testWidgets('短信未实际发送时明确提示暂未开放且不启动倒计时', (tester) async {
await tester.pumpWidget(MaterialApp(home: LoginPage(session: _UnavailableCodeSession())));
await tester.enterText(find.byType(TextField).first, '13800138000');
await tester.tap(find.text('获取验证码'));
await tester.pump();
expect(find.text('短信验证码暂未开放'), findsOneWidget);
expect(find.text('60s'), findsNothing);
});
}